基于React 源码深刻浅出setState:setState异步实现

做者 : 墨成javascript

React 版本 :16.4.1java

React启示录里咱们说setState是异步的,咱们在代码中也展现了这种特性,那么FB的工程师们是如何实现呢,本文将基于React的源码进一步揭开这层面纱。react

在介绍以前咱们首先看下setState的实现和FB工程师的注释,我简单的做了一些翻译程序员

//ReactBaseClass.js
/** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * //咱们应该使用这个方法(setState)来改变state,而不是使用this.state(翻译可能跟原文有一点误差,当表达的意思是这样) * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. *//setState并不会当即更新,在调用这个方法后拿到的this.state可能仍是原来的值
 * There is no guarantee that calls to `setState` will run synchronously,
 * as they may eventually be batched together.  You can provide an optional
 * callback that will be executed when the call to setState is actually
 * completed.
 *//setState不能保证是同步的,他们有可能会批量处理。你能够提供一个可选的回调函数来拿到更改以后的值
 * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. *//setState第一参数是一个function ,他会在将来的某个时间点执行。在执行这个functon时咱们都是拿到的最新的组件信息 *//(好比state,props, context).这些值根尼经过this.state的不同,由于function实在receiveProps以后在 *//shouldComponentDupdate以前,因此这些值还没更新到当前this指向的这些值 * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */ Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};复制代码

 上面的注释告诉咱们:chrome

1.setState()第一参数是function也是异步的promise

2.function的执行周期是:receiveProps=>function=>shouldComponentUpdate浏览器

3. 基于第二点的说明,再回头看看官网对function的写法,咱们知道为何这个函数的第一参数叫 preState ,而props 就叫props而不是叫preProps的缘由了.

(prevState, props) => stateChange复制代码

若是关于第一参数function的说明还不是很理解,多看几眼,多想一想React生命周期,那就会茅塞顿开 .bash

言归正传,再看看整段代码架构

Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};复制代码

参数:异步

@param  partialState 部分状态复制代码

这个参数或许是整个state,或许只是state其中的一部分 ,最终React合并(就是merge).

state = {name:'myName',age:18} => setState({age:16}/*state的一部分*/)=>state={name:'myName',age:16}复制代码

其实就是Object.assign(源码:Object.assign({}, prevState, partialState))的理解 .

@param  callback 回调函数,后面专门开一篇来说解复制代码

invariant这段代码主要对参数做了一些验证 ,因此 setState()只接受三种类型的参数,好比 object,function和与null 恒等的,好比undefined ,false . 若是你使用这三种断定类型以外的状况,会优雅的提示你错误,好比下面这个代码 :

this.setState(Symbol());复制代码

错误信息以下:



this.updater.enqueueSetState(this, partialState, callback, 'setState');复制代码

在这里 调用了 this.updater中的enqueueSetState,看着名字就知道这是一个setState的队列(准确的说它是一个链表),而这个 updater

// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;复制代码

上面的注释很清楚地告诉你 咱们有个默认的初始值,真正的值其实就在renderer的时候注入进来的,在下一篇文章中我会专门针对这个updater进入深刻理解,如今咱们来了解下这个updater的结构

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueReplaceState(inst, payload, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.tag = ReplaceState;
    update.payload = payload;

    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'replaceState');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  enqueueForceUpdate(inst, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'forceUpdate');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
};复制代码


总结一下:这个setState其实什么都没作,它只是简单的把这个update操做装入了一个"队列"里(看上去是这样,不是吗?).

那个问题来了,为何它就是异步的呢?JavaScript不是单线程吗 ?

先抛开React Fiber 架构不谈,咱们来聊聊 Javascript的 Event Loop  ,你们能够在网上找找关于 Event loop,在这里我重点推荐是 Philip Roberts 在JSConf上关于Eevent loop深情并茂的介绍(YouTube)

那首先你们在网上看到的资料无怪乎把task queue分为microtask和macrotask,同时他们也有个很好听的名字:微任务和宏任务(实际上是在外语中他们只是用这个词表示优先级),宏任务的优先级大于微任务。而后会把setTimeout,setInterval等等归于 macrotask,把promise归于mircotask.

在这里我表示,他们的论点放在单个浏览器,好比说chrome是对的,可是你们想过没有,对Promise的原生态支持是ES6(也有超前意识的浏览器厂商),实际状况是每一个浏览器对他们的处理不同(每一个浏览器的程序员不同嘛),你们能够把相同的代码放在不一样的浏览器,输出的结果是不同的 


这里你只要记住两点 :

1.全部的native code回调(window对象上的code)都是 macrotask,好比setTimeout ,Promise,setInterval

2.native code的优先级要大于普通回调函数

React启示录里我有说咱们能够"同步"(这里的同步不是说他直接在stack的调用方式,而是看上去同步拿到告终果)拿到state变化后的值,如今把咱们上一节部分代码做一点修改:

changeValue=()=>{
   setTimeout(()=>{
       this.setState(
             {value:'I have a new value by setTimeout'}
       );
        console.log(this.state.value);
    },0)

    new Promise((resolve,reject)=>{
       resolve();
    }).then(()=>{
       this.setState(
            {value:'I have a new value by promise '}
        );
        console.log(this.state.value);
    });
};

//result:
I have a new  value by promise 
I have a new  value by setTimeout
复制代码

这里并无等待this.setState()队列执行便可得到修改后的值,请务必在本身的代码中执行,由于我说的多是错的。


事实上,setState到这里已经完成了使命,剩下的全部任务都都交给了这个updater,updater是何方神圣,又是如何工做,它与react 16提出的fiber有什么样的关系 ,reconciler又是什么东西 ?

持续更新中......不要走开,全面了解 react的实现原理,不但能够帮助你更好的使用和优化React ,更能够了解它的实现架构和设计原理,并运用到实际的项目中 .

相关文章
相关标签/搜索