前言
我们在 React 使用 setState时知道,它并不总是异步更新,也可能是同步更新,大家也知道setState使用异步是为累积更新、批量处理、减少调用次数来提升性能,但是大家有没有想过setState的异步更新真的单单是为了性能吗?
setState API
setState(updater, [callback])
updater
(state, props) => stateChange
updater 函数中接收的 state 和 props 都保证为最新。updater 的返回值会与 state 进行浅合并。
callback
setState() 的第二个参数为可选的回调函数,它将在 setState 完成合并并重新渲染组件后执行。通常,我们建议使用 componentDidUpdate() 来代替此方式。
异步
setState将对组件state的更改排入队列,并通知React需要使用更新后的state重新渲染组件及其子组件。为了更好的性能,setState并不是立即进行更新,而是使用批量推迟更新。
看几个异步的例子,这些都是经常出现在面试中的代码例子(源码测试)
class Test extends Component { state = { count: 0 }; componentDidMount() { this.setState({ count: 1 }, () => { console.log(this.state.count); //1 } ); console.log(this.state.count); // 0 } render() {} }
class Test extends Component { state = { count: 0 }; componentDidMount() { this.setState( { count: this.state.count + 1 }, () => { console.log(this.state.count); // 1 } ); this.setState( { count: this.state.count + 1 // 这里的count还是0,并不会拿到更新后的count }, () => { console.log(this.state.count); // 1 } ); } render() {} }
class Test extends React.Component { state = { count: 0 }; componentDidMount() { this.setState( (preState) => { console.log(preState.count); // 0 return { count: preState.count + 1 }; }, () => { console.log(this.state.count); // 2 } ); this.setState( (preState) => { console.log(preState.count); // 1 return { count: preState.count + 1 }; }, () => { console.log(this.state.count); // 2 } ); } render() { return <div>1</div>; } }码
当调用setState函数时,就会把当前的操作放入到队列中,React 根据内容合并state数据,完成之后在逐一执行回调,根据结果去更新需求DOM,触发渲染。这里采用的是异步的方法,根据说法是异步是为了累积更新,批量处理,减少渲染次数,提升性能。
难道同步就不能累积更新、批量处理了吗
这里其实我们换一个角度来想想,难道同步就不能累积更新、批量处理了吗?难道同步就不能减少渲染次数,提升性了吗?
这个问题其实不止我有疑问,很早之前有大佬就已经在Issues上有提出这个问题:
当然在这个 Issues 下面 gaearon 大佬回复了这个问题:
回答的比较长,大体总结了两个方面:
1. 保持一致性
如果改为同步更新的方法,虽然setState是同步的,但是props不是。
2. 为后续的架构升级启用并发更新
为了完成一部的渲染,React会在触发 setState 的时候更新数据来源分配不同的优先级。这些数据的来源有:事件回调句柄、动画效果等,在根据优先级并发处理,提升渲染性能。