参考教程:React Tutorial: An Overview and Walkthrough
什么是 React
- React 是一个用于构建用户界面的 JavaScript 库
- React 属于 MVC 架构应用中的视图层
- 在 React 中,我们可以创建 components(自定义的、可复用的 HTML 元素)来快速和高效地构建用户界面
React 中使用 state 和 props 简化数据的存储和处理
安装和使用
静态 HTML 文件
简单易懂,像使用 JQuery 一样,不需要熟悉 Webpack,Babel,Node.js。
首先创建一个index.html文件,在<head>中加载三个 CDNs:React,React DOM,Babel;然后创建一个类名为root的div元素,最后在script元素写自定义代码。
引入的库:React:提供最上层的 API
- React DOM:给 React 添加针对于 DOM 的方法
Babel:JavaScript 编译器,将 ES6+ 转换为 ES5
<!DOCTYPE html><html><head><meta charset="utf-8" /><title>Hello React!</title><script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script><script src="https://unpkg.com/react-dom@16.13.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script></head><body><div id="root"></div><script type="text/babel">// React code will go here</script></body></html>
我们习惯将
rootdiv 元素作为 App 的入口点;text/babel的作用是使用 Babel。
接下来,使用 ES6 class 创建一个App组件: ```jsx class App extends React.Component { render() { return (<h1>Hello, world!</h1>
); } }
ReactDOM.render(
效果:<br /><a name="tLBRe"></a>### Create React App上面我们在静态 HTML 文件加载 JavaScript 库来渲染,但是这样的方式并不高效,也难以维护。幸运的是,Facebook 创建了一个 [Create React App](https://github.com/facebook/create-react-app)(常称之为脚手架),帮我们预设了一些构建 React App 需要的配置,它创建了一个 live development serve,使用 Webpack 自动解析和打包静态文件(React,JSX,ES6),并且使用 ESlint 来规范代码。<br />开始使用:```bashnpx create-react-app react-tutorialcd react-tutorialnpm start
项目目录:
public 文件夹中的 index.html 相当于我们第一种方式中的静态文件,src 中的就是 React code,还有一些其它的文件是工程项目必须的。
我们将 src 中的文件全部删除,仅创建 index.js 和 index.css 文件,然后再编写代码:
import React, { Component } from 'react'import ReactDOM from 'react-dom'import './index.css'class App extends Component {render() {return (<div className="App"><h1>Hello, React!</h1></div>)}}ReactDOM.render(<App />, document.getElementById('root'))
React Developer Tools
这是一个辅助 React 开发的浏览器插件,当项目变大时,非常有必要使用它来帮助我们调试。
JSX: JavaScript + XML
使用 JSX,我们可以在 JS 代码中编写类似于 HTML 元素的东西,我们也可以创建和使用 XML-like-tags,如下可以直接声明一个变量:
const heading = <h1 className="site-heading">Hello, React!</h1>
当然,在写 React 时 JSX 不是强制的。在底层,它其实是使用 createElement进行工作的,上面声明变量的代码相当于:
const heading = React.createElement('h1', { className: 'site-heading' }, 'Hello, React!')
它的参数包含标签,包含各种属性的对象,该组件的孩子。
JSX 实际上更接近于 JavaScript,而不是 HTML,所以我们写一些键时需要注意一下:
className:因为class是 JavaScript 中的关键字onClick:属性和方法使用 camelCase- 标签一定要闭合,如:
<img />
在 JSX 中,我们能够使用花括号嵌入 JavaScript 代码,包括变量、函数、属性:
const name = "Tom"const heading = <h1>Hello, {name}</h1>
组件
Class Component
class ClassComponent extends Component {render() {return <div>Example</div>}}
Simple Component
const SimpleComponent = () => {return <div>Example</div>}
Props
上面的 Table组件中,表格主体的数据都是写死的,在 React 中,重要的事情就是如何使用 props 和 state 处理数据,这里我们来看看 props 如何使用。
首先将 tbody 硬编码好的数据删除,然后将数据定义为一个对象数组放在 App 组件的 render 函数中,最后通过 properties 将其传到子组件。我们使用花括号在其中编写 JavaScript 表达式,所以不能使用 JavaScript 保留的关键字。
class App extends Component {render() {const characters = [{name: 'Charlie',job: 'Janitor'},{name: 'Mac',job: 'Bouncer'},{name: 'Dee',job: 'Aspring actress'},{name: 'Dennis',job: 'Bartender'}]return (<div className="container"><Table characterData={characters} /></div>)}}
此时,数据就被传到 Table组件中,我们可以打开 React DevTools,观察到 Table 中是有数据的,这些数据被存储在一个叫虚拟 DOM 的东西中,虚拟 DOM 是一种用于快速高效地同步真实 DOM 的方法。
现在这些数据还没有存在于真实 DOM 中,我们可以通过 this.props 来获取所有的 props,上面我们只传递了一个 characterData,所以我们可以使用 this.props.characterData 来取到那个数据,也可以使用 ES6 的结构赋值创建一个变量。
因为 Table 组件由两个更小的组件组成,所以我们要再传一次,把数据传到 TableBody 组件中:
class Table extends Component {render() {const { characterData } = this.propsreturn (<table><TableHeader /><TableBody characterData={characterData} /></table>)}}
然后我们应该在 TableBody 组件中使用这些数据,通过 map 遍历这个数据对象:
const TableBody = (props) => {const rows = props.characterData.map((row, index) => {return (<tr key={index}><td>{row.name}</td><td>{row.job}</td></tr>)}) qreturn <tbody>{rows}</tbody>}
可以看到我们在每个 table row 上使用了 index 作为 key 属性的值,它的作用是用来标识列表元素的,我们应该在制作列表时都使用 key 属性,并且明白当我们操作列表元素时它的重要性。
Props 是一种将已存在的数据传递给 React 组件的有效的方式,但是组件不能够改变 props,它们是只读的。
State
上面我们将数据存储在了一个数组中,并将它传递给了组件作为 props,且它是只读的,设想一下如果我们想要删除数组中的一个元素。使用 props,是单向数据流,但是使用 state 我们能够更新来自一个组件的私有数据。
我们可以使用将 state 定义为类的公共属性,相当于在 constructor 中定义属性,添加到类的实例上;然后创建一个修改 state 的方法, 在方法中,必须使用 this.setState() 来更新数据,否则不生效。
class App extends Component {// 公共属性state = {characters: [{name: 'Charlie',job: 'Janitor'},{name: 'Mac',job: 'Bouncer'},{name: 'Dee',job: 'Aspring actress'},{name: 'Dennis',job: 'Bartender'}]}removeCharacter = (index) => {const { characters } = this.statethis.setState({characters: characters.filter((character, i) => {return i !== index})})}render() {const { characters } = this.statereturn (<div className="container"><Table characterData={characters} removeCharacter={this.removeCharacter} /></div>)}}
:::warning
注意这里有 this 指向问题:我们将 removeCharacter 作为 props 传递给子组件后,子组件通过解构赋值的方式将方法重新赋给了一个变量,就会引起 this 丢失问题,因此,我们需要使用箭头函数定义,箭头函数内部不存在 this,它在定义时就会保存父级上下文的 this。
:::
我们也可以用下面的等价方式:
class App extends Component {// 公共属性constructor() {super()this.state = {characters: [{name: 'Charlie',job: 'Janitor'},{name: 'Mac',job: 'Bouncer'},{name: 'Dee',job: 'Aspring actress'},{name: 'Dennis',job: 'Bartender'}]}this.removeCharacter = this.removeCharacter.bind(this)// 或者// this.removeCharacter = (index) => {// const { characters } = this.state// this.setState({// characters: characters.filter((character, i) => {// return i !== index// })// })// }}removeCharacter(index) {const { characters } = this.statethis.setState({characters: characters.filter((character, i) => {return i !== index})})}render() {const { characters } = this.statereturn (<div className="container"><Table characterData={characters} removeCharacter={this.removeCharacter} /></div>)}}
另外,因为在本项目中只有 App 和 Form 组件有自己的 state,所以我们把 Table 组件改写为 Simple Component:
const Table = (props) => {const { characterData, removeCharacter } = propsreturn (<table><TableHeader /><TableBody characterData={characterData} removeCharacter={removeCharacter} /></table>)}
接下来创建一个按钮绑定一个事件:
<button onClick={() => props.removeCharacter(index)}>Delete</button>
:::warning
注意 onClick 函数必须传递一个函数,否则它将会自动执行,在这里如果我们直接传递 removeCharacter(index),它会立即执行,所以我们需要使用箭头函数来装饰一下。当然,如果不需要给函数传参,直接写函数名就行。
:::
最后加一个添加数组元素的功能,其要更新的是 App 组件中的 state,所以要在 App 组件中定义一个方法传递到子组件供其调用:
handleSubmit = (character) => {this.setState({characters: [...this.state.characters, character]})}
import React, { Component } from 'react'class Form extends Component {initialState = {name: '',job: ''}state = this.initialStatehandleChange = (event) => {const { name, value } = event.targetthis.setState({[name]: value})}submitForm = (event) => {if (!this.state.name || !this.state.job) {window.alert('表单不能为空')} else {this.props.handleSubmit(this.state)this.setState(this.initialState)}event.target.blur()}render() {const { name, job } = this.statereturn (<form><label htmlFor="name">Name</label><inputtype="text"name="name"id="name"value={name}onChange={this.handleChange}/><label htmlFor="job">Job</label><inputtype="text"name="job"id="job"value={job}onChange={this.handleChange}/><input type="button" value="Submit" onClick={this.submitForm} /></form>)}}export default Form
请求 Api 获取数据
import React, { Component } from 'react'class App extends Component {state = {data: []}// The code is invoked after the component is mounted into the DOM treecomponentDidMount() {const url = 'https://en.wikipedia.org/w/api.php?action=opensearch&search=Seona+Dancing&format=json&origin=*'fetch(url).then((result) => result.json()).then((result) => {this.setState({data: result})})}render() {const { data } = this.stateconst result = data.map((entry, index) => {return <li key={index}>{entry}</li>})return <ul>{result}</ul>}}export default App
