参考教程: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>
我们习惯将
root
div 元素作为 App 的入口点;text/babel
的作用是使用 Babel。
接下来,使用 ES6 class 创建一个App
组件: ```jsx class App extends React.Component { render() { return (<h1>Hello, world!</h1>
); } }
ReactDOM.render(
效果:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22173021/1663728351171-a92d92bc-9271-4333-ace1-892a64c1f386.png#clientId=u17d52d69-79d7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=264&id=ueaf4bd09&margin=%5Bobject%20Object%5D&name=image.png&originHeight=593&originWidth=1085&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20880&status=error&style=none&taskId=u330009d9-10a2-46d0-8a11-ac81c6f0bc9&title=&width=483)
<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 />开始使用:
```bash
npx create-react-app react-tutorial
cd react-tutorial
npm 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.props
return (
<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>
)
}) q
return <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.state
this.setState({
characters: characters.filter((character, i) => {
return i !== index
})
})
}
render() {
const { characters } = this.state
return (
<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.state
this.setState({
characters: characters.filter((character, i) => {
return i !== index
})
})
}
render() {
const { characters } = this.state
return (
<div className="container">
<Table characterData={characters} removeCharacter={this.removeCharacter} />
</div>
)
}
}
另外,因为在本项目中只有 App
和 Form
组件有自己的 state,所以我们把 Table
组件改写为 Simple Component:
const Table = (props) => {
const { characterData, removeCharacter } = props
return (
<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.initialState
handleChange = (event) => {
const { name, value } = event.target
this.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.state
return (
<form>
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
id="name"
value={name}
onChange={this.handleChange}/>
<label htmlFor="job">Job</label>
<input
type="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 tree
componentDidMount() {
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.state
const result = data.map((entry, index) => {
return <li key={index}>{entry}</li>
})
return <ul>{result}</ul>
}
}
export default App