今日目标
✔ 掌握特殊的 props.children 属性。
✔ 掌握如何对 props 进行校验和设置默认值。
✔ 掌握生命周期概念与对应的钩子函数。
✔ 了解 setState 更新数据的表现。
✔ 掌握 TODOLIST 案例的编写。
children 属性
目标
掌握 props 中 children 属性的用法。
内容
组件的子节点会被当做是 children 属性传递到子组件内部。
在传递数据的时候 children 属性与普通的 prop 一样,值可以是任意类型例如数字、字符串、数组、JSX、函数等。
代码
1 | function Hello(props) { |
1 | ;<Hello children='我是子节点' /> |
props 校验
目标
了解为什么需要对 props 进行校验。
掌握如何对传递过来的 prop 进行校验。
为什么需要对 props 进行校验
对于组件来说,props 是外来的,无法保证组件使用者传入数据的格式正确,如果传入的数据格式不对,可能会导致组件内部报错,而组件的使用者不能很明确的知道错误的原因。
演示 props 校验的意义
- 校验前

- 校验后

如何对 props 进行校验
安装并导入
prop-types
包。使用
组件名.propTypes = {}
来给组件的 props 添加校验规则。校验规则通过
PropTypes
对象来指定。
App.jsx
1 | import React, { Component } from 'react' |
Test.jsx
1 | import React, { Component } from 'react' |
总结
为什么要对 props 进行校验?
如何对 props 进行校验?
常见校验规则
目标
了解常见的 props 校验规则。
内容
常见类型:number、string、bool、array、func、object。
React 元素类型(JSX):element。
必填项:isRequired。
特定结构的对象:shape({})。
1 | { |
props 默认值
目标
了解指定默认值的好处。
掌握给组件的 props 提供默认值的 2 种方式。
内容
通过 defaultProps
可以给组件的 props 设置默认值,在未传入 props 的时候生效。
1 | import React, { Component } from 'react' |
更建议写法(利用 JS 自身的能力来完成)。
1 | import React, { Component } from 'react' |
小结
好处:即便外部不传递也不至于程序报错;简化代码(有可能就是有一些数据是很常用的,这样的话指定默认值外界不需要每次都传递啦)。
指定默认值的 2 种方式是什么?推荐哪一种?
类的静态属性
目标
能够通过类的 static 语法简化 props 校验和默认值的写法。
内容
实例成员:通过实例才能访问的成员(属性或者方法),叫做实例成员。
静态成员:通过类或者构造函数本身才能访问的成员(一般是直接挂载到类上的或者通过 static 关键字定义的)。
1 | class Person { |
简写
1 | import React, { Component } from 'react' |
生命周期概述
目标
能够理解什么是生命周期和生命周期函数。
能够说出 React 中组件的生命周期总共有几个大的阶段。
内容
生命周期:一个事物从创建到最后消亡的整个过程,而组件的生命周期说的就是组件从被创建到挂载到页面中运行,再到组件卸载的过程。
意义:学习组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件中问题产生的原因等。
生命周期钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
只有类组件才有生命周期。

React 生命周期

小结
什么是生命周期?
React 中生命周期有几个阶段?常用的有几个钩子函数?
挂载阶段
目标
能够说出挂载阶段的钩子函数有哪几个。
掌握执行时机和作用分别是什么。
内容
挂载阶段常用的生命周期函数有 3 个,执行顺序是 constructor => render => componentDidMount。
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1. 初始化 state 2. 创建 Ref 等 |
render | 每次组件渲染都会触发 | 渲染 UI(注意: 不能直接调用 setState() ) |
componentDidMount | 组件挂载(完成 DOM 渲染)后 | 1. 发送网络请求 2.DOM 操作 |
演示
小结
挂载阶段常用的有几个生命周期函数?
分别是什么,一般用来干什么?
更新阶段
目标
能够说出更新阶段的钩子函数有哪几个。
掌握执行时机和作用分别是什么。
内容
更新阶段常用的生命周期函数有 2 个,执行顺序是 render => componentDidUpdate。
触发更新:
setState()
、forceUpdate()
、New props
(父组件进行了 render)。
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染都会触发 | 渲染 UI(与挂载阶段是同一个 render) |
componentDidUpdate | 组件更新(完成 DOM 渲染)后 | DOM 操作,可以获取到更新后的 DOM 内容,不要调用 setState |
演示
小结
更新阶段常用的有几个钩子函数,分别用来做什么,注意点是什么?
哪 3 个操作会触发组件的更新?
卸载阶段
目标
能够说出组件卸载阶段的钩子函数是什么。
明白在卸载阶段的钩子函数里面干什么。
内容
触发时机:组件从页面中消失。
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等、解绑事件等) |
演示
定时器和解绑事件。
小结
卸载阶段的生命周期函数是什么?
一般用来干什么?
setState 更新数据的表现
目标
能够了解 setState 更新数据的表现,异步和同步的场景。
异步表现
一般情况下(常见的在生命周期或合成事件处理函数中),通过
setState()
方法来更新数据,表现是异步的。当执行到 setState 这一行的时候,React 出于性能考虑,并不会马上进行调用来修改 state,而是先把这个以及后续的更新对象放到一个更新队列里面进行合并的操作,期间不影响后续代码的执行。
多次调用 setState(),只会触发一次重新渲染,所以无需担心多次进行
setState
会带来性能问题。
1 | // 初始 |
解释合并
1 | this.setState({ |
执行过程:先排队,再把排队中的数据进行合并,最后执行 1 次 setState。
同步表现
如果是在 setTimeout/setInterval 或者原生事件的回调中,表现出来是同步的。
App.js
1 | import React, { Component } from 'react' |
原生事件
1 | import React, { Component, createRef } from 'react' |
另一种同步的表现写法,了解即可!
1 | import React, { Component } from 'react' |
注意细节
setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序会在更新之前,导致在合成事件和钩子函数中不能立即拿到更新后的值,形成了所谓的“异步”。
setState 的批量更新优化也是建立在钩子函数、合成事件之上的,在 setTimeout/Promise 和原生事件中不会批量更新,在“异步”中如果对同一个值进行多次 setState,setState 的批量更新测量会对其进行覆盖,取最后一次的结果,如果同时 setState 多个不同的值,在更新时会对齐进行合并后批量更新。
需要注意的是:ReactV18 是 Automatic batching 全自动批处理,18 之前的版本是半自动批处理。
总结
setState 更新数据的表现一般是异步的,目的是什么?在哪些场景下更新数据的表现是同步的?
问题/现象:不能立即拿到更新后的数据;多次进行 setState 会进行合并的操作。
setState 推荐语法
目标
能够解决上面的两个问题/现象。
能够掌握 setState 箭头函数的语法。
第一个问题
通过 setState 第二个参数可以立即拿到更新后的数据。
场景:在状态更新后,依靠更新后的状态立即执行某个操作。
语法:
setState(updater[, callback])
。
1 | this.setState({}, () => { |
第二个问题
推荐:使用
setState((preState) => {})
语法。参数 preState: 上一个
setState
的结果。
1 | // 初始 |
这种语法依旧是异步的,不同的是通过 preState 可以获取到最新的状态。
案例练习
需求

步骤
搭建基本结构。
准备一个变量来控制
<input/>
框的显示隐藏。给按钮绑定点击事件,在事件回调里面修改这个变量的状态。
在事件回调里面,获取
<input/>
框 DOM 对象并调用其聚焦的方法。
代码
1 | import React, { Component, createRef } from 'react' |
小结
数据更新后,想依赖更新后的数据做一些操作,把处理逻辑写在哪里?
B 站评论列表
目标
对留言数据进行持久化。
步骤
每次数据更新完毕存储到本地。
组件挂载完成从本地获取数据,通过 setState 进行更新。
代码
1 | export default class App extends Component { |
TODOLIST
案例目标

模拟接口
- 安装。
1 | npm i -g json-server |
- 准备数据
data.json
。
1 | { |
- 启动服务。
1 | json-server data.json --port 8888 |
- 使用
1 | GET /todos |
PUT 和 PATCH 的差异
PUT 是全量修改,修改了数据中的某一项也要把其他的数据带过去,不然其他的数据会被干掉,PATCH 是补丁,传递什么只会修改什么。
静态结构
App.jsx
1 | import React, { Component } from 'react' |
styles/base.css
1 | hr { |
styles/index.css
1 | html, |
组件拆分
App.jsx
1 | import React, { Component } from 'react' |
请求数据并渲染
步骤
App.js
中请求数据并存储到 state 的 list 数组中。把 list 数组传递到
<TodoMain/>
组件。通过 propTypes 对传递过来的 list 数组进行校验。
循环渲染 list 数组。
App.jsx
1 | import React, { Component } from 'react' |
components/TodoMain.jsx
1 | import React, { Component } from 'react' |
添加功能
步骤
<TodoHeader/>
组件收集输入的数据。给 input 框绑定键盘事件,如果敲了回车,并且输入的内容不为空,则调用添加接口把数据传递过去。
清空输入的内容。
调用父组件重新获取数据并渲染的方法。
App.jsx
1 | import React, { Component } from 'react' |
components/TodoHeader.jsx
1 | import React, { Component } from 'react' |
删除功能
给删除按钮绑定点击事件,并把当前项 ID 传过去。
根据接收到的 ID 调用删除接口。
调用父组件获取数据并渲染的方法。
步骤
App.jsx
1 | import React, { Component } from 'react' |
components/TodoMain.jsx
1 | import React, { Component } from 'react' |
选中功能
给 checkbox 框绑定 onChange 事件,并传递过去当前项。
根据当前项的 id 和 done(记得取反)调用修改的接口。
调用父组件获取数据并渲染的方法。
components/TodoMain.jsx
1 | import React, { Component } from 'react' |
完整代码
App.jsx
1 | import React, { Component } from 'react' |
components/TodoHeader.jsx
1 | import React, { Component } from 'react' |
components/TodoMain.jsx
1 | import React, { Component } from 'react' |
components/TodoFooter.jsx
1 | import React, { Component } from 'react' |