image.png

阅读源码已经有一段时间了,基本都是利用工作之余看的,有什么收获呢?除了更加了解框架的底层实现,更重要的大概就是自信了!技术自信对于每个研发来说是非常重要的,这会影响你在工作过程中的表现和状态。

转回话题,setState可以说在React中是比较灵魂的角色,无论平时开发还是面试中,都会高频问题。初学者在使用setState的时候会踩一些坑,本文针对一些setState内部原理进行源码范围的探索吧~!

基本使用

setState的写法可以分为两类:

  1. setState(updater[, callback]):第一个参数是一个updater函数;第二个参数是个回调函数(可选)
  2. setState(stateChange[, callback]):第一个参数是一个对象;第二个参数同上(可选)

第二个参数都一样,是一个可选的回调函数,其将会在setState执行完成同时组件被重渲之后再执行。通常,对于这类逻辑,我们推荐使用componentDidUpdate来处理。

当state更新值依赖state和props时,使用第一种形式

  1. this.setState({count=> this.state.count+1})
  2. this.setState((prevState, props) => {
  3. return {count: prevState.count + props.step};
  4. });

setState之后,调用的生命周期链路:setState=>shouldComponentUpdate=>componentWillUpdate=>re-render=>componentDidUpdate
如果我们想阻止re-render,可以在shouldComponentUpdate中返回false


React官方建议把State当作是不可变对象,也就是说,当你有修改this.state的值的冲动的时候,你应该做的是:重新创建一个新值来赋给this.state。

原因

  • 不可变对象方便管理和调试,了解更多可参考这里
  • shouldComponentUpdate PureComponent 都是通过浅比较(对象引用) 避免频繁re-render

尤其是复杂数据类型,常见会改变原数组的方法:push pop shift unshift splice等

demo

  1. import * as React from "react";
  2. class SetStateDemo extends React.Component {
  3. constructor(props) {
  4. super(props);
  5. }
  6. state = {
  7. count: 0,
  8. };
  9. componentDidMount() {
  10. console.log("setState=====>componentDidMount");
  11. this.setState({ count: this.state.count + 1 });
  12. console.log('setState=====>1',this.state.count); // 0
  13. this.setState({ count: this.state.count + 1 });
  14. console.log('setState=====>2',this.state.count); // 0
  15. setTimeout(_ => {
  16. this.setState({ count: this.state.count + 1 });
  17. console.log('setState=====>3',this.state.count); // 2
  18. this.setState({ count: this.state.count + 1 });
  19. console.log('setState=====>4',this.state.count); // 3
  20. }, 0);
  21. }
  22. increment = () => {
  23. console.log("increment");
  24. this.setState({ count: this.state.count + 1 });
  25. console.log('setState=====>4',this.state.count); // 3
  26. this.setState({ count: this.state.count + 1 });
  27. console.log(this.state.count); // 3
  28. };
  29. render() {
  30. console.log("render");
  31. return <button onClick={this.increment}>{this.state.count}</button>;
  32. }
  33. }
  34. export default SetStateDemo;

打印可以看到:

  • setState更新之后,不会立即拿到更新后的值,说明setState是异步的(这里指合成事件和生命周期中)
  • 多次更新同一个状态,会合并成一次更新
  • setTimeout或者原生事件中,setState之后可以立即拿到更新后的值

    关于setState的疑惑

  1. state对象保存在哪儿?
  2. 为什么有时连续多次setState只有一次生效?
  3. state更新到底在哪一阶段实现?
  4. 执行完setState获取state的值能获取到吗?
  5. setState是同步的还是异步的?
  6. setState的callback执行时机

那就看看源码是怎么实现的吧~!

源码解读

点击Performance 查看面板
image.png
很清晰,可以看到setState的调用栈情况。

我们大概搂一眼,可以看到setState => enqueueState => scheduleUpdateOnFiber 之后一系列的动作就是 render和commit工作,render和commit阶段讲解 移步XXXX

我们的源码探索之路沿着这条调用栈看看setState到底做了什么?

来吧!

一旦用户的交互产生了更新,那么就会产生一个update对象去承载新的状态。多个update会连接成一个环装链表:updateQueue,挂载fiber上, 然后在该fiber的beginWork阶段会循环该updateQueue,依次处理其中的update,这是处理更新的大致过程

总结

https://react.iamkasong.com/state/prepare.html

https://github.com/neroneroffy/react-source-code-debug/blob/master/docs/render%E9%98%B6%E6%AE%B5/beginWork%E9%98%B6%E6%AE%B5/%E5%A4%84%E7%90%86%E6%9B%B4%E6%96%B0.md

https://github.com/chdyiboke/weekly/blob/master/2021-03%E5%91%A8-lkx-react%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8BsetState.md