react中执行setState更新状态时都做了什么?
结论:setState是react中修改组件状态、触发视图更新的一个api。他是一个异步更新的过程,但是不同于setTimeout、promise等异步Api实现,而是react自身的批量更新机制导致的异步,去触发react的更新流程(协调、渲染),从而实现的组件状态更新和视图的重新渲染。整个过程涉及,状态更新队列、自动批处理、调度协调机制等,具体流程请看下图(大概搂一眼现在不了解没事)
为什么是异步?看案例
核心表现:状态不会立即更新
class Example extends React.Component { state = { count: 0 }; handleClick = () => { console.log('点击前:', this.state.count); // 0 this.setState({ count: this.state.count + 1 }); console.log('点击后:', this.state.count); // 还是 0! }; }多个state执行什么表现?
handleClick = () => { this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); // 如果 count 初始是 0,最终只会变成 1,而不是 3 // 因为三次都基于同一个 this.state.count (0),引出概率自动批处理!!!这很重要!!! };关键点:setState调用后,this.state不会马上改变。React 会将多个setState调用合并(batch),在事件处理函数结束时统一更新。这就是上述说的是异步但是不同于setTimeout、promise等异步Api实现,而是react自身的批量更新机制(自动批处理)导致的异步原因。
什么是批量更新机制(自动批处理)?
这里的批量更新机制(自动批处理)需要分成react17及之前和react17后去理解,两者在批量更新时稍微有点不一样。
在 React 17 及之前,React 会判断当前是否处于批量更新上下文(合成事件、生命周期钩子)。如果是,则将多个setState合并到一次渲染中处理;如果不是(setTimeout、Promise、原生事件),则每次setState都会触发一次独立的渲染调度,但状态本身仍然是异步更新的,不会立即同步反映到 state上。React 18默认在所有场景下开启自动批处理,只要使用createRoot()挂载,即使在setTimeout或 Promise 中多次setState也只会触发一次渲染
看代码(有条件的同学可以自己在控制台打印看看)
一、合成事件中的打印(React 17 和 18 表现一致)不知道什么是合成事件的,可以去找资料看看,或者我后期会出。
import React, { Component } from 'react'; class Example extends Component { state = { count: 0 }; handleClick = () => { console.log('=== 合成事件开始 ==='); console.log('1. setState 前:', this.state.count); // 0 this.setState({ count: this.state.count + 1 }); console.log('2. 第1次 setState 后:', this.state.count); // 0 this.setState({ count: this.state.count + 1 }); console.log('3. 第2次 setState 后:', this.state.count); // 0 this.setState({ count: this.state.count + 1 }); console.log('4. 第3次 setState 后:', this.state.count); // 0 console.log('=== 合成事件结束 ==='); }; render() { console.log('render:', this.state.count); return ( <button onClick={this.handleClick}> 合成事件点击 (count: {this.state.count}) </button> ); } } === 合成事件开始 === 1. setState 前: 0 2. 第1次 setState 后: 0 3. 第2次 setState 后: 0 4. 第3次 setState 后: 0 === 合成事件结束 === render: 1二、setTimeout 中的打印(React 17 vs 18 差异)
1、React 17 表现(立即执行,非批量更新)
class Example extends Component { state = { count: 0 }; handleClick = () => { setTimeout(() => { console.log('=== setTimeout 开始 ==='); console.log('1. setState 前:', this.state.count); // 0 this.setState({ count: this.state.count + 1 }); console.log('2. 第1次 setState 后:', this.state.count); // 1 ← 已更新! this.setState({ count: this.state.count + 1 }); console.log('3. 第2次 setState 后:', this.state.count); // 2 ← 已更新! this.setState({ count: this.state.count + 1 }); console.log('4. 第3次 setState 后:', this.state.count); // 3 ← 已更新! console.log('=== setTimeout 结束 ==='); }, 0); }; render() { console.log('render:', this.state.count); return <button onClick={this.handleClick}>setTimeout 点击</button>; } } === setTimeout 开始 === 1. setState 前: 0 2. 第1次 setState 后: 1 render: 1 3. 第2次 setState 后: 2 render: 2 4. 第3次 setState 后: 3 render: 3 === setTimeout 结束 ===2、React 18 表现(自动批处理)
class Example extends Component { state = { count: 0 }; handleClick = () => { setTimeout(() => { console.log('=== setTimeout 开始 ==='); console.log('1. setState 前:', this.state.count); // 0 this.setState({ count: this.state.count + 1 }); console.log('2. 第1次 setState 后:', this.state.count); // 0 ← 未变! this.setState({ count: this.state.count + 1 }); console.log('3. 第2次 setState 后:', this.state.count); // 0 ← 未变! this.setState({ count: this.state.count + 1 }); console.log('4. 第3次 setState 后:', this.state.count); // 0 ← 未变! console.log('=== setTimeout 结束 ==='); }, 0); }; render() { console.log('render:', this.state.count); return <button onClick={this.handleClick}>setTimeout 点击</button>; } } 1. setState 前: 0 2. 第1次 setState 后: 0 3. 第2次 setState 后: 0 4. 第3次 setState 后: 0 === setTimeout 结束 === render: 13、核心对比总结
4、思考,为什么同样的代码react17和18为什么控制台表现不一致?
- 上述我们提到了,在 React 17 及之前,React 会判断当前是否处于批量更新上下文(合成事件、生命周期钩子)。如果是,则将多个
setState合并到一次渲染中处理;如果不是(setTimeout、Promise、原生事件),则每次setState都会触发一次独立的渲染调度,但状态本身仍然是异步更新的,不会立即同步反映到this.state上。React 18默认在所有场景下开启自动批处理,只要使用createRoot()挂载,即使在setTimeout或 Promise 中多次setState也只会触发一次渲染所以react17和18不一致!
现在我们再看,在调用setState时,react内部都经历了什么?
setState的本质是:创建 Update → 加入 Fiber 的 UpdateQueue → 请求调度 → 批量合并 → 工作循环遍历 Fiber 树计算新状态 → Diff 标记副作用 → Commit 阶段同步更新 DOM → 执行回调和 Effect→更新完成。
结合图看