参考教程: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;然后创建一个类名为 rootdiv元素,最后在 script 元素写自定义代码。
    引入的库:

  • React:提供最上层的 API

  • React DOM:给 React 添加针对于 DOM 的方法
  • Babel:JavaScript 编译器,将 ES6+ 转换为 ES5

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8" />
    5. <title>Hello React!</title>
    6. <script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
    7. <script src="https://unpkg.com/react-dom@16.13.0/umd/react-dom.production.min.js"></script>
    8. <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
    9. </head>
    10. <body>
    11. <div id="root"></div>
    12. <script type="text/babel">
    13. // React code will go here
    14. </script>
    15. </body>
    16. </html>

    我们习惯将 rootdiv 元素作为 App 的入口点;text/babel的作用是使用 Babel。
    接下来,使用 ES6 class 创建一个 App 组件: ```jsx class App extends React.Component { render() { return (

    1. <h1>Hello, world!</h1>

    ); } }

ReactDOM.render(, document.getElementById(‘root’));

  1. 效果:<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)
  2. <a name="tLBRe"></a>
  3. ### Create React App
  4. 上面我们在静态 HTML 文件加载 JavaScript 库来渲染,但是这样的方式并不高效,也难以维护。幸运的是,Facebook 创建了一个 [Create React App](https://github.com/facebook/create-react-app)(常称之为脚手架),帮我们预设了一些构建 React App 需要的配置,它创建了一个 live development serve,使用 Webpack 自动解析和打包静态文件(React,JSX,ES6),并且使用 ESlint 来规范代码。<br />开始使用:
  5. ```bash
  6. npx create-react-app react-tutorial
  7. cd react-tutorial
  8. npm start

项目目录:
image.png
public 文件夹中的 index.html 相当于我们第一种方式中的静态文件,src 中的就是 React code,还有一些其它的文件是工程项目必须的。
我们将 src 中的文件全部删除,仅创建 index.jsindex.css 文件,然后再编写代码:

  1. import React, { Component } from 'react'
  2. import ReactDOM from 'react-dom'
  3. import './index.css'
  4. class App extends Component {
  5. render() {
  6. return (
  7. <div className="App">
  8. <h1>Hello, React!</h1>
  9. </div>
  10. )
  11. }
  12. }
  13. ReactDOM.render(<App />, document.getElementById('root'))

然后项目自动进行编译,页面自动刷新,显示我们编写的内容:
image.png

React Developer Tools

这是一个辅助 React 开发的浏览器插件,当项目变大时,非常有必要使用它来帮助我们调试。

JSX: JavaScript + XML

使用 JSX,我们可以在 JS 代码中编写类似于 HTML 元素的东西,我们也可以创建和使用 XML-like-tags,如下可以直接声明一个变量:

  1. const heading = <h1 className="site-heading">Hello, React!</h1>

当然,在写 React 时 JSX 不是强制的。在底层,它其实是使用 createElement进行工作的,上面声明变量的代码相当于:

  1. const heading = React.createElement('h1', { className: 'site-heading' }, 'Hello, React!')

它的参数包含标签,包含各种属性的对象,该组件的孩子。
JSX 实际上更接近于 JavaScript,而不是 HTML,所以我们写一些键时需要注意一下:

  • className:因为 class 是 JavaScript 中的关键字
  • onClick:属性和方法使用 camelCase
  • 标签一定要闭合,如:<img />

在 JSX 中,我们能够使用花括号嵌入 JavaScript 代码,包括变量、函数、属性:

  1. const name = "Tom"
  2. const heading = <h1>Hello, {name}</h1>

组件

Class Component

  1. class ClassComponent extends Component {
  2. render() {
  3. return <div>Example</div>
  4. }
  5. }

Simple Component

  1. const SimpleComponent = () => {
  2. return <div>Example</div>
  3. }

PS:如果 return 只有一行,可以不用加括号

Props

上面的 Table组件中,表格主体的数据都是写死的,在 React 中,重要的事情就是如何使用 props 和 state 处理数据,这里我们来看看 props 如何使用。
首先将 tbody 硬编码好的数据删除,然后将数据定义为一个对象数组放在 App 组件的 render 函数中,最后通过 properties 将其传到子组件。我们使用花括号在其中编写 JavaScript 表达式,所以不能使用 JavaScript 保留的关键字。

  1. class App extends Component {
  2. render() {
  3. const characters = [
  4. {
  5. name: 'Charlie',
  6. job: 'Janitor'
  7. },
  8. {
  9. name: 'Mac',
  10. job: 'Bouncer'
  11. },
  12. {
  13. name: 'Dee',
  14. job: 'Aspring actress'
  15. },
  16. {
  17. name: 'Dennis',
  18. job: 'Bartender'
  19. }
  20. ]
  21. return (
  22. <div className="container">
  23. <Table characterData={characters} />
  24. </div>
  25. )
  26. }
  27. }

此时,数据就被传到 Table组件中,我们可以打开 React DevTools,观察到 Table 中是有数据的,这些数据被存储在一个叫虚拟 DOM 的东西中,虚拟 DOM 是一种用于快速高效地同步真实 DOM 的方法。
image.png
现在这些数据还没有存在于真实 DOM 中,我们可以通过 this.props 来获取所有的 props,上面我们只传递了一个 characterData,所以我们可以使用 this.props.characterData 来取到那个数据,也可以使用 ES6 的结构赋值创建一个变量。
因为 Table 组件由两个更小的组件组成,所以我们要再传一次,把数据传到 TableBody 组件中:

  1. class Table extends Component {
  2. render() {
  3. const { characterData } = this.props
  4. return (
  5. <table>
  6. <TableHeader />
  7. <TableBody characterData={characterData} />
  8. </table>
  9. )
  10. }
  11. }

然后我们应该在 TableBody 组件中使用这些数据,通过 map 遍历这个数据对象:

  1. const TableBody = (props) => {
  2. const rows = props.characterData.map((row, index) => {
  3. return (
  4. <tr key={index}>
  5. <td>{row.name}</td>
  6. <td>{row.job}</td>
  7. </tr>
  8. )
  9. }) q
  10. return <tbody>{rows}</tbody>
  11. }

可以看到我们在每个 table row 上使用了 index 作为 key 属性的值,它的作用是用来标识列表元素的,我们应该在制作列表时都使用 key 属性,并且明白当我们操作列表元素时它的重要性。
Props 是一种将已存在的数据传递给 React 组件的有效的方式,但是组件不能够改变 props,它们是只读的。

State

上面我们将数据存储在了一个数组中,并将它传递给了组件作为 props,且它是只读的,设想一下如果我们想要删除数组中的一个元素。使用 props,是单向数据流,但是使用 state 我们能够更新来自一个组件的私有数据。
我们可以使用将 state 定义为类的公共属性,相当于在 constructor 中定义属性,添加到类的实例上;然后创建一个修改 state 的方法, 在方法中,必须使用 this.setState() 来更新数据,否则不生效。

  1. class App extends Component {
  2. // 公共属性
  3. state = {
  4. characters: [
  5. {
  6. name: 'Charlie',
  7. job: 'Janitor'
  8. },
  9. {
  10. name: 'Mac',
  11. job: 'Bouncer'
  12. },
  13. {
  14. name: 'Dee',
  15. job: 'Aspring actress'
  16. },
  17. {
  18. name: 'Dennis',
  19. job: 'Bartender'
  20. }
  21. ]
  22. }
  23. removeCharacter = (index) => {
  24. const { characters } = this.state
  25. this.setState({
  26. characters: characters.filter((character, i) => {
  27. return i !== index
  28. })
  29. })
  30. }
  31. render() {
  32. const { characters } = this.state
  33. return (
  34. <div className="container">
  35. <Table characterData={characters} removeCharacter={this.removeCharacter} />
  36. </div>
  37. )
  38. }
  39. }

:::warning 注意这里有 this 指向问题:我们将 removeCharacter 作为 props 传递给子组件后,子组件通过解构赋值的方式将方法重新赋给了一个变量,就会引起 this 丢失问题,因此,我们需要使用箭头函数定义,箭头函数内部不存在 this,它在定义时就会保存父级上下文的 this。 ::: 我们也可以用下面的等价方式:

  1. class App extends Component {
  2. // 公共属性
  3. constructor() {
  4. super()
  5. this.state = {
  6. characters: [
  7. {
  8. name: 'Charlie',
  9. job: 'Janitor'
  10. },
  11. {
  12. name: 'Mac',
  13. job: 'Bouncer'
  14. },
  15. {
  16. name: 'Dee',
  17. job: 'Aspring actress'
  18. },
  19. {
  20. name: 'Dennis',
  21. job: 'Bartender'
  22. }
  23. ]
  24. }
  25. this.removeCharacter = this.removeCharacter.bind(this)
  26. // 或者
  27. // this.removeCharacter = (index) => {
  28. // const { characters } = this.state
  29. // this.setState({
  30. // characters: characters.filter((character, i) => {
  31. // return i !== index
  32. // })
  33. // })
  34. // }
  35. }
  36. removeCharacter(index) {
  37. const { characters } = this.state
  38. this.setState({
  39. characters: characters.filter((character, i) => {
  40. return i !== index
  41. })
  42. })
  43. }
  44. render() {
  45. const { characters } = this.state
  46. return (
  47. <div className="container">
  48. <Table characterData={characters} removeCharacter={this.removeCharacter} />
  49. </div>
  50. )
  51. }
  52. }

另外,因为在本项目中只有 AppForm 组件有自己的 state,所以我们把 Table 组件改写为 Simple Component:

  1. const Table = (props) => {
  2. const { characterData, removeCharacter } = props
  3. return (
  4. <table>
  5. <TableHeader />
  6. <TableBody characterData={characterData} removeCharacter={removeCharacter} />
  7. </table>
  8. )
  9. }

接下来创建一个按钮绑定一个事件:

  1. <button onClick={() => props.removeCharacter(index)}>Delete</button>

:::warning 注意 onClick 函数必须传递一个函数,否则它将会自动执行,在这里如果我们直接传递 removeCharacter(index),它会立即执行,所以我们需要使用箭头函数来装饰一下。当然,如果不需要给函数传参,直接写函数名就行。 ::: 最后加一个添加数组元素的功能,其要更新的是 App 组件中的 state,所以要在 App 组件中定义一个方法传递到子组件供其调用:

  1. handleSubmit = (character) => {
  2. this.setState({
  3. characters: [...this.state.characters, character]
  4. })
  5. }
  1. import React, { Component } from 'react'
  2. class Form extends Component {
  3. initialState = {
  4. name: '',
  5. job: ''
  6. }
  7. state = this.initialState
  8. handleChange = (event) => {
  9. const { name, value } = event.target
  10. this.setState({
  11. [name]: value
  12. })
  13. }
  14. submitForm = (event) => {
  15. if (!this.state.name || !this.state.job) {
  16. window.alert('表单不能为空')
  17. } else {
  18. this.props.handleSubmit(this.state)
  19. this.setState(this.initialState)
  20. }
  21. event.target.blur()
  22. }
  23. render() {
  24. const { name, job } = this.state
  25. return (
  26. <form>
  27. <label htmlFor="name">Name</label>
  28. <input
  29. type="text"
  30. name="name"
  31. id="name"
  32. value={name}
  33. onChange={this.handleChange}/>
  34. <label htmlFor="job">Job</label>
  35. <input
  36. type="text"
  37. name="job"
  38. id="job"
  39. value={job}
  40. onChange={this.handleChange}/>
  41. <input type="button" value="Submit" onClick={this.submitForm} />
  42. </form>
  43. )
  44. }
  45. }
  46. export default Form

最终效果图:
image.png

请求 Api 获取数据

  1. import React, { Component } from 'react'
  2. class App extends Component {
  3. state = {
  4. data: []
  5. }
  6. // The code is invoked after the component is mounted into the DOM tree
  7. componentDidMount() {
  8. const url = 'https://en.wikipedia.org/w/api.php?action=opensearch&search=Seona+Dancing&format=json&origin=*'
  9. fetch(url)
  10. .then((result) => result.json())
  11. .then((result) => {
  12. this.setState({
  13. data: result
  14. })
  15. })
  16. }
  17. render() {
  18. const { data } = this.state
  19. const result = data.map((entry, index) => {
  20. return <li key={index}>{entry}</li>
  21. })
  22. return <ul>{result}</ul>
  23. }
  24. }
  25. export default App