React State
- React 是单向数据流,自顶向下数据流,由父节点向子节点由上到下传递,通过更新 React 组件状态,实现重新渲染用户界面操作。
- React 组件可以看成一个*** 状态机 ***(组件通过与用户交互,实现不同的状态,然后通过渲染 UI 保证用户界面和数据的一致性)。
- React 通过 setState() 方法来更新状态(state)
state 初始化
state 设计的几点原则;
- state 代表一个组件 UI 呈现的完整的最小状态集,是渲染组件时所用到的数据的来源
- 用作组件 UI 展现形式的判断依据,反应组件的 UI 变化没有任何中间状态,也不存在通过其他状态计算来的中间状态
React16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import React,{Component} from 'react';
export default class pop extends Component {
// construct 用于初始化组件的 state及绑定事件
construct(props) {
// 调用父类的 construct 方法,即调用 react.Component 的构造方法。用于完成 reasct 组件的初始化工作,保证 props 传入组件
super(props);
this.state = {
isShow: false, // 展现形式的判断依据
pop: '弹层', // 渲染时所用到的数据
purchaseList: [],
totalCost: 0 // 根据 purchaseList 中 的 price 计算得来的,属性于中间多余属性。
}
}
render() {
const {isShow, pop} = this.state;
return (
isShow ? <div>{pop}</div> : null
)
}
}React18
1
2
3
4
5
6
7
8
9
10
11export default class Demo extends React.Component {
state = {
x: 0,
y: 0,
z: 0
}
render() {
return <>...</>
}
}
setState
1 | /** |
1 | Component.prototype.setState = function (partialState, callback) {...} |
setState 回调函数 callback 的执行机制
无论 shouldComponentUpdate 返回 true
还是 false
,callback 都会执行
flowchart TB start((开始)) callback["this.setState({},callback)"] start --> callback callback --> shouldComponentUpdate{shouldComponentUpdate} shouldComponentUpdate -->|yes| componentWillUpdate[componentWillUpdate] componentWillUpdate --> render[render] shouldComponentUpdate -->|no 返回执行callback| callback callback --> render render --> componentDidUpdate[componentDidUpdate]
setState 执行是异步还是同步
使用 React 内置的 setState() 修改 state,每当使用 setState() 时,React 会将需要更新的 state 合并后放入状态队列,触发调和过程(Reconciliation),再根据新的状态结构重新渲染 UI 界面,React 会根据差异对界面进行最小化重新渲染
React18 中
1 | export default class Demo extends React.Component { |
React18 中,setState 执行是 异步操作
React18 的更新队列处理机制,基于异步操作,最终实现批处理从而减少更新次数,降低性能消耗。
flowchart TB subgraph "setState" sentences1["this.setState({x: ++x})"] sentences2["this.setState({y: ++y})"] sentences3["this.setState({z: ++z})"] end subgraph "任务队列Reconciliation" task1[修改x] task2[修改y] task3[修改z] end sentences1 -->|添加任务到队列|task1 sentences2 -->|添加任务到队列|task2 sentences3 -->|添加任务到队列|task3 render[render] 任务队列Reconciliation -->|批处理| render
React16 中
1 | export default class App extends React.Component { |
React16 对于异步操作(如:定时器,通过 addEventListener 手动绑定 Dom 事件) setState 的处理方式与 React18 不同,它会变成同步
flowchart TB subgraph "setState" sentences1["this.setState({x: ++x})"] sentences2["this.setState({y: ++y})"] sentences3["setTimeout(() => { this.setState({z: ++z}) console.log('setState z:', z) // render2 --> setState 同步 });"] end subgraph "任务队列Reconciliation" task1[修改x] task2[修改y] end sentences1 -->|添加任务到队列|task1 sentences2 -->|添加任务到队列|task2 render[render] 任务队列Reconciliation -->|批处理| render sentences3 -->|同步|render
flushSync 强制刷新执行渲染
依赖当前的 state 计算下一个 state
采用
flushSync
强制刷新执行渲染
1 | import {flushSync} from 'React-dom' |
代码执行顺序
flowchart TB step1["this.setState({x: ++x});"] -->|"state x 1"| updater step2["this.setState({y: ++y})"] -->|"state y 1"| updater updater -->|flushSync| render render --> step3["this.setState({z: x + y}) "] step3 --> render render -->|"state z 4"| e((end)) style step1 fill:orange,stroke: orange style step2 fill:orange,stroke:orange style step3 fill:orange,stroke:orange style render fill:#f9f, stroke: #f9f
点击一次 btn,页面渲染 20 次
1 | export default class Demo extends React.Component { |
更新一次,渲染结果 20
flowchart TB updater[" updater 队列 prevState => { let {x, y, z} = prevState return { x: ++x, y: ++y, z: ++z } }, prevState => { let {x, y, z} = prevState return { x: ++x, y: ++y, z: ++z } }, ... prevState => { let {x, y, z} = prevState return { x: ++x, y: ++y, z: ++z } } "]
1 | this.setState(prevState => { |
1 | export default class Demo extends React.Component { |
关于 state props 与使用普通属性的一些忠告
触发渲染的属性是 props 和 state,props 来自父级,只读属性,不能修改。如果需要修改须在父级 setState 修改,state 维护组件内部属性。
- 变量是否从 props 中获取,若是,则不是一个属性;
- 变量是否在组件的整个生命周期中保持不变,若是,则不是一个状态;
- 状态是否是从在组件 render 中使用,未使用,则不是一个状态,适合用普通属性。
1 | import React, {Component} from 'react'; |
简而言之,就是 state 如同函数内的变量,props 如同入参,state 和 props 共同触发 ui 渲染
this.setState({…}) 修改 state,获取 state 属性 this.state.xx
setState() 两个参数, prevState 和 props 分别用于传递前一个 state 和 props 参数
react 官方建议把 state 当作不可变对象
state 中包含的所有状态都应该是不可变对象,state 中的某个状态发生变化时,应该重新创建这个状态对象,并非是修改原来的状态。将状态类型分为三种情况创建新的状态对象:
状态类型是数字、字符串、布尔值、null、undefined
直接给修改状态赋新值1
2
3
4
5this.setState({
quantity: 1,
title: 'react',
success: true
})状态类型是数组
状态发生变化时重新创建状态对象,数组方法中,push, pop, shift, unshift, splice 都是
在原数组的基础上修改的
,concat、slice、filter 是返回一个新数组
。1
2
3
4
5
6
7
8
9
10
11
12/* books 是要修改的状态,类型为数组 */
// 使用 preState、concat 创建新数组
// 箭头之后的圆括号用来实现换行(MDN)
this.setState((preState) => ({
books: preState.books.concat(['react guide'])
}))
// ES6 spread syntax
this.setState((preState) => ({
books: [...preState.books, 'react guide']
}))用
slice
从数组中截取一部分元素作为新状态;用filter
过滤部分元素作作为新状态1
2
3
4
5
6
7
8
9this.setState((preState) => ({
books: preState.books.slice(1,3)
}))
this.setState(preState => ({
books: preState.books.filter(item => {
return item !== 'react'
})
}))状态类型是不包含字符串、数组的普通对象
避免修改原对象的方法,使用返回一个新对象的方法1
2
3
4
5
6
7
8
9// 使用 ES6 Object.assgin 方法
this.setState(preState => ({
owner: Object.assgin({}, preState.owner, {name: 'helen'})
}))
// 使用对象扩展语法 object spread properties
this.setState(preState => ({
owner: {...preState.owner, {name: 'helen'}}
}))react 推荐组件状态不可变的原因在于,返回新对象可以避免原有对象组件状态不小心情况下修改,导致错误,方便管理、调试。还有出于性能考虑,当组件对象不可变时,在组件的 shouldComponentUpdate 方法中仅需要对比前后对象的引用便可以判断是否发迹,避免不必要的 render.