说说 React setState 的原理?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 setState 异步特性的理解
- 对 setState 批量更新机制的理解
- 对 React 更新流程的理解
- 对 state 更新合并策略的理解
核心概念(基础知识点)
setState 是什么
setState 是 React 类组件中用于更新状态的方法。它会触发组件的重新渲染,但更新可能是异步的,并且可能会被批量处理。
setState 的核心特点
- 异步更新: setState 不会立即更新 state,而是将更新加入队列
- 批量处理: 多个 setState 调用可能会被合并为一次更新
- 状态合并: 新的 state 会与旧 state 进行浅合并
详细解答(代码示例)
setState 的基本使用
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: 'React'
};
}
// 基本用法:传入对象
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
// 传入函数(推荐)
handleClickBetter = () => {
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
};
// 传入回调函数
handleClickWithCallback = () => {
this.setState(
{ count: this.state.count + 1 },
() => {
console.log('State updated:', this.state.count);
}
);
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
setState 的异步特性
class AsyncDemo extends React.Component {
state = { count: 0 };
handleClick = () => {
console.log('Before setState:', this.state.count); // 0
this.setState({ count: this.state.count + 1 });
console.log('After setState:', this.state.count); // 仍然是 0!
};
// 连续多次调用 setState
handleMultiple = () => {
// 以下三次调用,count 只增加了 1(React 18 之前)
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
// 结果:count = 1,而不是 3
};
// 使用函数形式解决
handleMultipleCorrect = () => {
// 每次更新都基于最新的 state
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
// 结果:count = 3
};
render() {
console.log('render:', this.state.count);
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Test Async</button>
<button onClick={this.handleMultiple}>Multiple (Wrong)</button>
<button onClick={this.handleMultipleCorrect}>Multiple (Correct)</button>
</div>
);
}
}
setState 何时是同步的
class SyncDemo extends React.Component {
state = { count: 0 };
// React 18 之前:在 setTimeout 中是同步的
handleTimeout = () => {
setTimeout(() => {
console.log('Before:', this.state.count); // 0
this.setState({ count: this.state.count + 1 });
console.log('After:', this.state.count); // 1(同步更新)
}, 0);
};
// 原生事件中是同步的
componentDidMount() {
document.getElementById('native-btn').addEventListener('click', () => {
console.log('Before:', this.state.count);
this.setState({ count: this.state.count + 1 });
console.log('After:', this.state.count);
});
}
// React 合成事件中是异步的
handleSynthetic = () => {
console.log('Before:', this.state.count); // 0
this.setState({ count: this.state.count + 1 });
console.log('After:', this.state.count); // 0(异步)
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleSynthetic}>Synthetic Event</button>
<button onClick={this.handleTimeout}>SetTimeout</button>
<button id="native-btn">Native Event</button>
</div>
);
}
}
React 18 的自动批处理
// React 18 中所有更新都会自动批处理
class React18Demo extends React.Component {
state = { count: 0, flag: false };
handleClick = () => {
// 在 React 18 中,这些更新会被自动批处理
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
this.setState({ flag: !this.state.flag });
// 只会触发一次 render
}, 0);
};
// 使用 flushSync 强制同步更新
handleFlushSync = () => {
import('react-dom').then(({ flushSync }) => {
flushSync(() => {
this.setState({ count: this.state.count + 1 });
});
// 这里可以立即获取到更新后的值
console.log(this.state.count);
flushSync(() => {
this.setState({ flag: !this.state.flag });
});
});
};
render() {
console.log('render');
return (
<div>
<p>Count: {this.state.count}</p>
<p>Flag: {this.state.flag.toString()}</p>
<button onClick={this.handleClick}>Auto Batch</button>
<button onClick={this.handleFlushSync}>Flush Sync</button>
</div>
);
}
}
深入理解(原理剖析)
setState 执行流程
┌─────────────────────────────────────────────────────────────┐
│ setState 执行流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 调用 setState(newState) │
│ │ │
│ ▼ │
│ 2. 将更新加入队列(enqueueSetState) │
│ │ │
│ ▼ │
│ 3. 判断是否处于批量更新模式 │
│ │ │
│ ┌─────┴─────┐ │
│ ▼ ▼ │
│ 是(批量) 否(同步) │
│ │ │ │
│ ▼ ▼ │
│ 加入队列 立即执行 │
│ 延迟执行 batchedUpdates │
│ │ │
│ ▼ │
│ 4. 批量更新结束后,统一执行 │
│ │ │
│ ▼ │
│ 5. 合并 state(Object.assign) │
│ │ │
│ ▼ │
│ 6. 触发重新渲染(re-render) │
│ │
└─────────────────────────────────────────────────────────────┘
setState 源码简化版
// 简化版 setState 实现原理
// 1. setState 入口
Component.prototype.setState = function(partialState, callback) {
// 将 setState 加入更新队列
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// 2. 更新队列处理
const updater = {
enqueueSetState(inst, payload, callback) {
// 创建更新对象
const update = {
payload,
callback,
next: null
};
// 加入队列
const queue = inst._internalFiber.updateQueue;
if (queue === null) {
inst._internalFiber.updateQueue = update;
} else {
// 添加到队列末尾
let last = queue;
while (last.next) {
last = last.next;
}
last.next = update;
}
// 调度更新
scheduleUpdateOnFiber(inst._internalFiber);
}
};
// 3. 批量更新处理
let isBatchingUpdates = false;
let batchedUpdatesQueue = [];
function scheduleUpdateOnFiber(fiber) {
if (isBatchingUpdates) {
// 批量更新模式:加入队列,延迟执行
batchedUpdatesQueue.push(fiber);
} else {
// 非批量模式:立即执行
performSyncWorkOnRoot(fiber);
}
}
// 4. 批量更新执行
function flushBatchedUpdates() {
isBatchingUpdates = false;
// 处理所有队列中的更新
while (batchedUpdatesQueue.length > 0) {
const fiber = batchedUpdatesQueue.shift();
performSyncWorkOnRoot(fiber);
}
}
批量更新机制
// React 合成事件中的批量更新
class BatchDemo extends React.Component {
state = { count: 0 };
handleClick = () => {
// React 合成事件会自动开启批量更新
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
// 结果:count = 1(三次更新合并为一次)
};
render() {
console.log('render:', this.state.count);
return (
<button onClick={this.handleClick}>
Count: {this.state.count}
</button>
);
}
}
// 批量更新的实现原理
function batchedUpdates(fn, a, b, c, d, e) {
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return fn(a, b, c, d, e);
} finally {
// 事件处理结束后,统一执行更新
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates) {
flushBatchedUpdates();
}
}
}
状态合并策略
// setState 使用 Object.assign 进行浅合并
// 初始 state
this.state = {
count: 0,
name: 'React',
user: {
age: 18
}
};
// setState 只更新 count
this.setState({ count: 1 });
// 合并后的 state(浅合并)
{
count: 1, // 更新
name: 'React', // 保留
user: { // 保留(引用不变)
age: 18
}
}
// 注意:嵌套对象需要手动合并
// 错误的更新方式
this.setState({ user: { age: 19 } });
// 结果:user 对象被完全替换,其他属性丢失
// 正确的更新方式
this.setState(prevState => ({
user: {
...prevState.user,
age: 19
}
}));
最佳实践
1. 使用函数式更新
class Counter extends React.Component {
state = { count: 0 };
// 推荐:使用函数形式
increment = () => {
this.setState(prevState => ({
count: prevState.count + 1
}));
};
// 连续更新时,函数形式确保正确
incrementThree = () => {
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
// 结果:count 增加 3
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increment}>+1</button>
<button onClick={this.incrementThree}>+3</button>
</div>
);
}
}
2. 获取更新后的状态
class UpdateCallback extends React.Component {
state = { count: 0 };
handleClick = () => {
// 方式1:使用回调函数
this.setState(
{ count: this.state.count + 1 },
() => {
console.log('Updated:', this.state.count);
// 可以在这里执行基于新状态的操作
}
);
// 方式2:使用 componentDidUpdate
};
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('Count changed:', this.state.count);
}
}
render() {
return (
<button onClick={this.handleClick}>
Count: {this.state.count}
</button>
);
}
}
3. 避免直接修改 state
class StateMutation extends React.Component {
state = {
items: [1, 2, 3],
user: { name: 'John', age: 30 }
};
// 错误:直接修改 state
wrongUpdate = () => {
this.state.items.push(4); // 直接修改!
this.setState({ items: this.state.items });
};
// 正确:创建新对象
correctUpdate = () => {
this.setState({
items: [...this.state.items, 4]
});
};
// 正确:更新嵌套对象
updateUser = () => {
this.setState(prevState => ({
user: {
...prevState.user,
age: 31
}
}));
};
render() {
return (
<div>
<button onClick={this.correctUpdate}>Add Item</button>
</div>
);
}
}
4. 状态提升与拆分
// 避免一个组件管理过多状态
class BadComponent extends React.Component {
state = {
user: {},
posts: [],
comments: [],
ui: { loading: false, error: null }
};
// ...
}
// 推荐:拆分状态到不同组件或使用多个 state
function GoodComponent() {
const [user, setUser] = useState({});
const [posts, setPosts] = useState([]);
const [comments, setComments] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 或者使用自定义 Hook
const userState = useUser();
const postsState = usePosts();
// ...
}
面试要点
-
setState 是异步的: 不会立即更新 state,而是将更新加入队列
-
批量更新: 多个 setState 可能会被合并为一次更新
- React 合成事件中自动批量更新
- React 18 中所有更新都自动批处理
-
状态合并: 使用 Object.assign 进行浅合并
- 嵌套对象需要手动展开
-
两种调用方式:
- 对象形式:
setState({ count: 1 }) - 函数形式:
setState(prev => ({ count: prev.count + 1 })) - 连续更新时推荐使用函数形式
- 对象形式:
-
获取更新后的值:
- setState 回调函数
- componentDidUpdate 生命周期
- 或 await Promise(不直接支持)
-
React 18 变化:
- 自动批处理扩展到所有场景
- 使用 flushSync 强制同步更新