阅读 3092

react的setstate原理

之前写react的时候,踩了几次坑发现setstate之后state不会立刻更新,于是判定setstate就是异步的方法,但是直到有一天,我想立刻拿到更新的state去传参另一个方法的时候,才问自己,为什么setstate是异步的? 准确地说,在React内部机制能检测到的地方, setState就是异步的;在React检测不到的地方,例如setInterval,setTimeout里,setState就是同步更新的。

1.setState在react生命周期和合成事件会批量覆盖执行

所谓合成事件就是:react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件

举个🌰 当遇到多个setstate调用的时候会提取单次传递setstate对象,就像Object.assign的对象合并,相同最后一个key会覆盖前面的key

const a = {name : 'kong', age : '17'}
const b = {name : 'fang', sex : 'men'}
Object.assign({}, a, b);
//{name : 'fang', age : '17', sex : 'men'}
复制代码

2.setstate在原生事件,setTimeout,setInterval,promise等异步操作中,state会同步更新

当执行到 setTimeout 的时候,把它丢到列队里,并没有去执行,而是先执行的 finally 主进程代码块,等 finally 执行完了, isBatchingUpdates 又变为了 false ,导致最后去执行队列里的 setState 时候, requestWork 走的是和原生事件一样的 expirationTime === Sync if分支,所以表现就会和原生事件一样,可以同步拿到最新的state的值。

3.setState批量更新的过程

在react生命周期和合成事件执行前后都有相应的钩子,分别是pre钩子和post钩子,pre钩子会调用batchedUpdate方法将isBatchingUpdates变量置为true,开启批量更新,而post钩子会将isBatchingUpdates置为false

setstate更新流程

一、setstate方法

ReactComponent.prototype.setState = function (partialState, callback) {
  //  将setState事务放进队列中
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};
复制代码

这里的partialState会产生新的state以一种Object.assgine()的方式跟旧的state进行合并。

二、enqueueSetState

 enqueueSetState: function (publicInstance, partialState) {
     // 获取当前组件的instance
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

     // 将要更新的state放入一个数组里
     var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

     //  将要更新的component instance也放在一个队列里
    enqueueUpdate(internalInstance);
  }
复制代码

三、enqueueUpdate

function enqueueUpdate(component) {
 // 如果没有处于批量创建/更新组件的阶段,则处理update state事务
 if (!batchingStrategy.isBatchingUpdates) {
   batchingStrategy.batchedUpdates(enqueueUpdate, component);
   return;
 }
 // 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
 dirtyComponents.push(component);
}
复制代码

isBatchingUpdates变量置为true,则会走批量更新分支,setState的更新会被存入队列中,待同步代码执行完后,再执行队列中的state更新。 isBatchingUpdates 为 true,则把当前组件(即调用了 setState 的组件)放入 dirtyComponents 数组中;否则 batchUpdate 所有队列中的更新

四、batchingStrategy

var ReactDefaultBatchingStrategy = {
  // 用于标记当前是否出于批量更新
  isBatchingUpdates: false,
  // 当调用这个方法时,正式开始批量更新
  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // 如果当前事务正在更新过程在中,则调用callback,既enqueueUpdate
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
    // 否则执行更新事务
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};
复制代码

五、事务

batchedUpdates 发起 transaction.perform() 事务
这个事务流程中的 anyMethod 是 runBatchedUpdates ,即更新组件状态并走一遍组件生命周期,在 componentWillMount 时将 A 状态队列里累积的状态都依次处理了。
wrapper close,循环遍历 dirtyComponents 并执行 transaction.perform(runBatchedUpdates, null, transaction);,于是 A 就被捡起来开始走事务流程
直到 dirtyComponents 里最后一个组件跑完流程,组件都被统一更新了一遍。

复制代码

4.最后来一个测验

(1)

屏幕快照 2018-10-11 下午6.38.22.png

看此代码的执行顺序
上面代码打印结果
22在33的前面, async函数返回的也是一个promise对象,对!就是返回一个promise,所以promise是一个异步方法,里面的环境里,setstate是同步的

(2)前几天自己忽然写出来个自己都不确定的执行顺序😃(写bug。。。)
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: false
    }
  }
  componentWillMount() {
    

      // 获取业务数据
  }

  onClick = () => {
    setTimeout(() => {this.setState({ loading: true }, () => {console.log('1-setState');})}, 4000)
    
    new Promise(resolve => { 
      this.setState({ loading: false }, () => {console.log('3-setState');});
      console.log('2');
      resolve(4);

    })
    .then((res) => {
      this.setState({ loading: false }, () => {console.log('2-setState');});
      
      console.log(res, '////res');
    });
    console.log('1');
  }

  render() {
    return <Button type="primary" loading={this.state.loading} onClick={this.onClick}>;all 起来了</Button>;
      
  }
}

export default App;
复制代码

promise里面的then方法源码是将传入的方法包在一个setTimeout里面,为了立即执行then方法return出来promise去做递归 因此这个环境下setstate是同步的 所以2-setstate早于下面一行的console.log(res, '////res');

有兴趣的同学可以研究一下本人梳理并注释的promise源码 github.com/xinqiymsz/p…

文章分类
前端
文章标签