1 setState基本用法
React中的setState
是用于更新组件状态的API,是类组件中管理状态的主要方式。在React 18中,setState有三种主要使用形式:
1.1 对象式setState
javascript
this.setState({
count: this.state.count + 1
});
1.2 函数式setState
javascript
this.setState((prevState, props) => {
return {
count: prevState.count + 1
};
});
1.3 带回调的setState
javascript
this.setState({
count: this.state.count + 1
}, () => {
console.log('状态更新完毕', this.state.count);
});
函数式setState允许你基于先前的状态和属性计算新状态,这特别适用于多次状态更新依赖先前状态的情况。带回调的setState则在状态更新和界面重新渲染后执行,可用于获取更新后的状态值。
2 React18中setState的特性
2.1 异步批处理
在React 18中,setState默认是异步操作,且无论在什么环境下执行(合成事件、生命周期、定时器、原生事件等),都会自动进行批处理。
javascript
handleClick = () => {
// 三次setState会被批处理为一次更新
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
// 此时state.count还是原来的值
console.log(this.state.count); // 0
// 通过回调函数获取更新后的值
this.setState({ count: this.state.count + 1 }, () => {
console.log(this.state.count); // 1(批处理后的结果)
});
}
2.2 与React16行为的对比
场景 | React 16 | React 18 |
---|---|---|
合成事件/生命周期 | 异步批处理 | 异步批处理 |
定时器/原生事件 | 同步立即更新 | 异步批处理 |
多次setState | 部分合并 | 自动批处理 |
React 18通过自动批处理机制显著减少了不必要的渲染,提高了性能。
3 setState原理与源码分析
3.1 核心流程概述
setState调用后会触发一系列的更新流程,主要包括以下几个阶段:
- 更新调度:setState调用创建更新对象,并加入到更新队列中,然后发起调度请求。
- 协调阶段(Render Phase):React计算新的虚拟DOM树,与旧的树进行对比(diff算法),找出需要更新的部分。
- 提交阶段(Commit Phase):将协调阶段确定的变更应用到真实DOM上。
graph LR
A[setState调用] --> B[创建Update对象]
B --> C[加入Fiber更新队列]
C --> D[调度更新]
D --> E[协调阶段: 计算新状态和虚拟DOM]
E --> F[提交阶段: 更新DOM]
F --> G[执行回调函数]
3.2 源码关键函数分析
3.2.1 setState入口
setState方法位于ReactBaseClasses.js
中:
javascript
Component.prototype.setState = function(partialState, callback) {
// 将更新和回调放入队列
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
3.2.2 更新入队(enqueueSetState)
enqueueSetState
函数负责创建更新对象并将其加入队列:
javascript
enqueueSetState: function(inst, payload, callback) {
const fiber = getInstance(inst); // 获取对应的Fiber节点
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber); // 获取更新优先级
// 创建更新对象
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
// 将更新对象加入Fiber节点的更新队列
enqueueUpdate(fiber, update);
// 调度更新
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
3.2.3 处理更新队列(processUpdateQueue)
在渲染阶段,React会处理更新队列并计算新状态:
javascript
function processUpdateQueue(workInProgress, props, instance, renderLanes) {
const queue = workInProgress.updateQueue;
let newBaseState = queue.baseState;
let newFirstBaseUpdate = null;
// 遍历更新队列,计算新状态
let update = queue.firstUpdate;
while (update !== null) {
const nextUpdate = update.next;
// 处理更新对象,计算新状态
if (update.payload !== null) {
// 函数式更新
if (typeof update.payload === 'function') {
const nextState = update.payload.call(instance, newBaseState, props);
newBaseState = Object.assign({}, newBaseState, nextState);
} else {
// 对象式更新
newBaseState = Object.assign({}, newBaseState, update.payload);
}
}
update = nextUpdate;
}
// 更新Memoized状态
workInProgress.memoizedState = newBaseState;
}
3.3 状态合并机制
React使用浅合并策略来合并状态更新:
javascript
// 假设初始状态为 {a: 1, b: 2}
this.setState({b: 3});
this.setState({c: 4});
// 合并后的状态为 {a: 1, b: 3, c: 4}
合并过程使用Object.assign()
实现:
javascript
newState = Object.assign({}, previousState, partialState);
3.4 异步批处理机制
React 18通过优先级调度 和双缓冲机制实现高效的批处理:
- 优先级调度:React使用车道模型(Lane Model)为不同更新分配优先级,高优先级更新可以打断低优先级更新。
- 双缓冲机制:React同时维护两颗Fiber树(当前树和工作树),避免直接操作正在渲染的树。
4 特殊情况处理
4.1 在异步代码中的setState
即使在异步代码(如setTimeout)中,React 18也会批处理setState调用:
javascript
handleClick = () => {
// 在React事件处理函数中
this.setState({ count: this.state.count + 1 });
setTimeout(() => {
// 在setTimeout中,React 18也会批处理
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
}, 0);
};
// 最终count只会增加1,而不是3(因为批处理)
4.2 强制同步更新
在某些特殊情况下,可以使用flushSync
强制同步更新:
javascript
import { flushSync } from 'react-dom';
handleClick = () => {
// 默认批处理
this.setState({ count: this.state.count + 1 });
// 强制同步更新
flushSync(() => {
this.setState({ count: this.state.count + 1 });
});
// 这里可以获取到同步更新后的值
console.log(this.state.count);
};
5 性能优化与最佳实践
5.1 避免不必要的渲染
- 使用
shouldComponentUpdate
或PureComponent
避免不必要的重新渲染 - 使用
React.memo
缓存函数组件
5.2 状态更新优化技巧
- 函数式更新:当新状态依赖旧状态时,使用函数式更新
javascript
// 推荐:使用函数式更新
this.setState((prevState) => ({
count: prevState.count + 1
}));
// 不推荐:直接依赖this.state
this.setState({
count: this.state.count + 1
});
- 合理使用回调函数:在回调函数中处理更新后的逻辑
javascript
this.setState(
{ count: 10 },
() => {
console.log('更新完成', this.state.count);
// 执行其他依赖新状态的操作
}
);
- 避免深层状态嵌套:使用浅拷贝更新深层状态
javascript
// 更新深层状态
this.setState(prevState => ({
user: {
...prevState.user,
profile: {
...prevState.user.profile,
age: 25
}
}
}));
总结
React 18中的setState通过自动批处理机制 和优先级调度,显著提高了性能表现。其核心原理是基于Fiber架构和双缓冲机制,通过创建更新对象、加入更新队列、调度更新和处理更新队列等步骤实现状态更新。
关键要点:
- React 18中setState在任何情况下都是异步的,且会自动批处理。
- setState通过浅合并 更新状态,使用函数式更新确保基于最新状态计算。
- 更新流程分为调度阶段 、协调阶段 和提交阶段。
- 使用回调函数 或生命周期方法获取更新后的状态值。
理解setState的内部机制有助于编写更高效、可靠的React应用程序,避免常见的状态管理陷阱。