We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
所谓同步还是异步指的是调用 setState 之后是否马上能得到最新的 state。
setState
state
setState 的“异步”并不是说内部由异步代码实现,相反其执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”。
可以通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。:
setState(partialState, callback)
callback
this.setState((state, props) => ({ counter: state.counter + props.increment }));
一个具体例子:
handleClickOnLikeButton () { this.setState((prevState) => { return { count: 0 } }) this.setState((prevState) => { return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1 }) this.setState((prevState) => { return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3 }) // 最后的结果是 this.state.count 为 3 }
先说结论:既可以异步更新也可以同步更新
异步:
concurrent
同步:(legacy 模式下才生效)
legacy
异步代码中调用(如setTimeout, Promise, MessageChannel等)
setTimeout
Promise
MessageChannel
异步代码中调用 setState,由于js的异步处理机制,异步代码会暂存,等待同步代码执行完毕再执行,此时 React 的批处理机制已经结束,因而直接更新。
监听原生事件而非 React 的合成事件,在原生事件的回调函数中执行 setState 就是同步的。原因是原生事件不会触发 React 的批处理机制,因而调用 setState 会直接更新
ReactDOM.render(<App />, rootNode)
ReactDOM.createBlockingRoot(rootNode).render(<App />)
ReactDOM.createRoot(rootNode).render(<App />)
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
setState()
当 this.setState() 被调用的时候,React 会调用 render 方法来重新渲染UI,渲染 UI 的过程其实就是操作 DOM 的过程,DOM 的操作对性能的损耗是非常严重的,所以 React 为了提高整体的渲染性能,会将一次渲染周期中的 state 进行合并,再一次性的渲染,这样可以避免频繁调用 setState 导致频繁的操作 DOM 了,从而提高渲染性能。
this.setState()
render
至于实现原理,由于代码不断的更新优化,也许以后还会更新,所以知道有这个逻辑就好,需要的时候再去阅读当前版本的代码。平时只要知道不需要担心多次进行 setState 会带来性能问题就行。
比如之前是通过 isBatchingUpdates 这个布尔属性判断是否合并更新。到后来更新 Fiber 架构后,是否同步更新的判断逻辑放进了 ReactFiberWorkLoop 中,最新又有变动,重构了 Fiber.expirationTime 并引入 Fiber.lanes。源码位置
isBatchingUpdates
ReactFiberWorkLoop
Fiber.expirationTime
Fiber.lanes
在线查看
class App extends React.Component { constructor(props) { super(props); this.state = { count: 0, }; this.handleAddCount = this.handleAddCount.bind(this); this.btnRef = React.createRef(); } componentDidMount() { this.handleAddCount(null, 'a'); setTimeout(this.handleAddCount, 1 * 1000); Promise.resolve().then(() => { this.handleAddCount(null, 'b'); }); const button = this.btnRef.current; if (button) { button.addEventListener("click", this.handleAddCount); } } componentWillUnmount() { const button = this.btnRef.current; if (button) { button.removeEventListener("click", this.handleAddCount); } } handleAddCount(e, key = 'c') { console.log(`${key}-0`, this.state.count); this.setState({ count: this.state.count + 1 }); console.log(`${key}-1`, this.state.count); this.setState({ count: this.state.count + 100 }); console.log(`${key}-2`, this.state.count); } render() { return ( <div> <p>count: {this.state.count}</p> <div> <button onClick={this.handleAddCount}>React 合成事件改变 state</button> <button ref={this.btnRef}>addEventListener 事件改变 state</button> </div> </div> ); } } const domContainer = document.querySelector("#app"); // legacy 模式 ReactDOM.render(<App />, domContainer);
扩展:下面 4 次打印都是什么值?详细讨论
class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次 log setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次 log }, 0); } render() { return null; } };
分情况:
0 0 2 3
0 0 1 1
The text was updated successfully, but these errors were encountered:
No branches or pull requests
所谓同步还是异步指的是调用
setState
之后是否马上能得到最新的state
。setState 的异步
setState
的“异步”并不是说内部由异步代码实现,相反其执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”。可以通过第二个参数
setState(partialState, callback)
中的callback
拿到更新后的结果。:一个具体例子:
先说结论:既可以异步更新也可以同步更新
异步:
concurrent
模式下都为异步 concurrent 模式Demo同步:(
legacy
模式下才生效)异步代码中调用(如
setTimeout
,Promise
,MessageChannel
等)异步代码中调用
setState
,由于js的异步处理机制,异步代码会暂存,等待同步代码执行完毕再执行,此时 React 的批处理机制已经结束,因而直接更新。监听原生事件而非 React 的合成事件,在原生事件的回调函数中执行
setState
就是同步的。原因是原生事件不会触发 React 的批处理机制,因而调用setState
会直接更新三种模式(当前 v17)
ReactDOM.render(<App />, rootNode)
。这是当前 React app 使用的方式。当前没有计划删除本模式,但是这个模式可能不支持这些新功能。ReactDOM.createBlockingRoot(rootNode).render(<App />)
。目前正在实验中。作为迁移到 concurrent 模式的第一个步骤。ReactDOM.createRoot(rootNode).render(<App />)
。目前在实验中,未来稳定之后,打算作为 React 的默认开发模式。这个模式开启了所有的新功能。原因
当
this.setState()
被调用的时候,React 会调用render
方法来重新渲染UI,渲染 UI 的过程其实就是操作 DOM 的过程,DOM 的操作对性能的损耗是非常严重的,所以 React 为了提高整体的渲染性能,会将一次渲染周期中的state
进行合并,再一次性的渲染,这样可以避免频繁调用setState
导致频繁的操作 DOM 了,从而提高渲染性能。至于实现原理,由于代码不断的更新优化,也许以后还会更新,所以知道有这个逻辑就好,需要的时候再去阅读当前版本的代码。平时只要知道不需要担心多次进行
setState
会带来性能问题就行。比如之前是通过
isBatchingUpdates
这个布尔属性判断是否合并更新。到后来更新 Fiber 架构后,是否同步更新的判断逻辑放进了ReactFiberWorkLoop
中,最新又有变动,重构了Fiber.expirationTime
并引入Fiber.lanes
。源码位置在线查看
扩展:下面 4 次打印都是什么值?详细讨论
分情况:
0 0 2 3
0 0 1 1
参考
The text was updated successfully, but these errors were encountered: