基于React 源码深入浅出setState:深度刨析updater的结构和原理

栏目: 服务器 · 发布时间: 5年前

内容简介:作者 : 墨成   React 版本 :16.4.1阅读本文之前,建议阅读:

作者 : 墨成   React 版本 :16.4.1

阅读本文之前,建议阅读:

1.基于React 源码深入浅出setState:官方文档的启示录

2.基于React 源码深入浅出setState:setState异步实现

在上一篇 详细了解了setState的一些机制和原理,同时对 updater  也作了简单的解释,这篇文章我们将详细了解这个updater的数据结构和调用堆栈.

代码如下:

react\packages\react-reconciler\src\ReactFiberClassComponent.js
复制代码
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);
  },
};复制代码

代码并不长,但是包含的信息还是比较多,首先我们知道这个 updater 是个常量对象,而且在整个React它是单例的,即所有组件都是用同一个 updater (注意与后面提及的update区别),updater中包含了3个非常重要的方法  enqueueSetState, enqueueForceUpdate, enqueueReplaceState  准确的说是包含了一个重要的方法  enqueueSetState

我们首先看下 在setState中调用的 enqueueSetState(inst, payload, callback)

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

实际的 enqueueSetState(inst, payload, callback)只有三个参数,第四个参数是为默认的 ReactNoopUpdateQueue  准备的,可以忽略。

/**
** @param {?react component} 组件事例 
* @param {object|function} payload 对应的就是 partialState
* @param {?function} callback 回调函数 
 
*/
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);
}复制代码
  1. 前三个变量 fiber,currentTime,expirationTime

const fiber = ReactInstanceMap.get(inst);

ReactInstanceMap拿到Component的Fiber实例 ,它其实等于

const fiber = inst._reactInternalFiber;

基于React 源码深入浅出setState:深度刨析updater的结构和原理

FB的工程师们通过 ReactInstanceMap这样的实现应该是考虑到后续的拓展和对私有变量取值的封装,后续 ReactInstanceMap 应该会使一个类似<key,value>的键值对数据类型 .

const currentTime = requestCurrentTime();复制代码

React通过Performance.now()(如果浏览器不支持这个API ,则是用Date.now())获取时间值(说明:返回当前日期和时间的 Date 对象与'1970/01/01 00:00:00'之间的毫秒值(北京时间的时区为东8区,起点时间实际为:'1970/01/01 08:00:00'),所以这个currentTime是个number类型,即typeof currentTime === 'number' )

const expirationTime = computeExpirationForFiber(currentTime, fiber);复制代码

expirationTime 根据当前时间和 fiber计算它的 有效时间. expirationTime  涉及的体系比较复杂,后续会结合实际的代码讲解.

2.Update对象

const update = createUpdate(expirationTime);
update.payload = payload;复制代码

创建一个 update 的对象,数据结构在下面会有提及,这里的payLoad 还是partialState

export function createUpdate(expirationTime: ExpirationTime): Update<*> {
  return {
    expirationTime: expirationTime,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
    nextEffect: null,
  };
}复制代码

3. enqueueUpdate&scheduleWork

故事讲到这里,应该很快进入高潮,两个比较核心的看点

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

在了解这两个函数之前,我们首先看下两个数据结构

位置:react\packages\react-reconciler\src\ReactUpdateQueue.js

export type Update<State> = {
  expirationTime: ExpirationTime,//有效时间

  tag: 0 | 1 | 2 | 3,//tag  1 2 3 4 其中之一 
  payload: any,//update的partialState
  callback: (() => mixed) | null,//setState的回调函数

  next: Update<State> | null,//指向下一个 update的指针
  nextEffect: Update<State> | null,//指向下一个effect(变化) 
};

export type UpdateQueue<State> = {
  baseState: State,//就是Component初始化的state

  firstUpdate: Update<State> | null,
  lastUpdate: Update<State> | null,

  firstCapturedUpdate: Update<State> | null,
  lastCapturedUpdate: Update<State> | null,

  firstEffect: Update<State> | null,
  lastEffect: Update<State> | null,

  firstCapturedEffect: Update<State> | null,
  lastCapturedEffect: Update<State> | null,
};复制代码

每个update有个指向下一个update的"指针" next,  UpdateQueue<State> 是个单向链表,firstxxx  lastxxx分别指向链表头部和尾部. enqueueUpdate(fiber, update),所以很多人 错误的认为updateQuenue 是一个(类)数组,   代码如下 :

//以下注释仅对 ClassComponent有效(即你的组件是继承React.Component)
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // Update queues are created lazily. Update queues被延迟创建(即这个组件没有update 我们没有必要给它fiber来创建这样属性) 
  const alternate = fiber.alternate;
  let queue1;//设计它是为了指向current的updateQueue
  let queue2; //设计它是为了指向alternate 的updateQueue
  if (alternate === null) {//alternate为null,对于classcomponent第一次调用setState时alternate为null
    // There's only one fiber.
    queue1 = fiber.updateQueue;
    queue2 = null;
    if (queue1 === null) {//首次调用setState创建updateQueue,这时的memoizedState为初始值
      queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
    }
  } else {
    // There are two owners.
    queue1 = fiber.updateQueue;//current Fiber updateQueue 
    queue2 = alternate.updateQueue;
    if (queue1 === null) {
      if (queue2 === null) {
        // Neither fiber has an update queue. Create new ones.
        //如果都为空,则分别创建,这个case我没有找到
        queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
        queue2 = alternate.updateQueue = createUpdateQueue(
          alternate.memoizedState,
        );
      } else {
        // Only one fiber has an update queue. Clone to create a new one.
        // 如果有一个有update Queue,则克隆一个 
        queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
      }
    } else {
      if (queue2 === null) {
        // Only one fiber has an update queue. Clone to create a new one.
        queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
      } else {
        // Both owners have an update queue.
      }
    }
  }
  if (queue2 === null || queue1 === queue2) { 
    // There's only a single queue.
    //一般发生在首次 setState
    appendUpdateToQueue(queue1, update);
  } else {
    // There are two queues. We need to append the update to both queues,
    // while accounting for the persistent structure of the list — we don't
    // want the same update to be added multiple times.
     // 翻译:如果存在两个queues,我们需要追加这个 update到这个两个 queues.
      //然而对于这种持久性结构的列表(updateQueue)需要保证一次update不能添加多次 
    if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
      // One of the queues is not empty. We must add the update to both queues.
      appendUpdateToQueue(queue1, update);
      appendUpdateToQueue(queue2, update);
    } else {
      // Both queues are non-empty. The last update is the same in both lists,
      // because of structural sharing. So, only append to one of the lists.
      appendUpdateToQueue(queue1, update);
      // But we still need to update the `lastUpdate` pointer of queue2.
      queue2.lastUpdate = update;
    }
  }

  if (__DEV__) {
    if (
      fiber.tag === ClassComponent &&
      (currentlyProcessingQueue === queue1 ||
        (queue2 !== null && currentlyProcessingQueue === queue2)) &&
      !didWarnUpdateInsideUpdate
    ) {
      warningWithoutStack(
        false,
        'An update (setState, replaceState, or forceUpdate) was scheduled ' +
          'from inside an update function. Update functions should be pure, ' +
          'with zero side-effects. Consider using componentDidUpdate or a ' +
          'callback.',
      );
      didWarnUpdateInsideUpdate = true;
    }
  }
}复制代码

关于alternate ( 参考 React Fiber Architecture ) 

基于React 源码深入浅出setState:深度刨析updater的结构和原理

简单的概括下:在任何时候,一个组件的实例最多应对两个fiber:current,alternate(flushed fiber or work-in-progress fiber,不同阶段的叫法),alternate是延迟创建并从 cloneFiber这个方法中克隆出来的 ,也就是所谓的persitent structure,他们结构共享(Structural Sharing).

Structural Sharing 和 延迟创建贯穿整个React  ,比如这里

FB 的工程师有段注释:

// Update queues are created lazily.复制代码

update queues是延迟创建,要注意的是,这里的延迟创建不仅仅是对current,对alternate的queue也是一样.

scheduleWork 涉及调度器的知识体系,后续学习完成补充着部分的欠缺 。

参考资料 :

1. React Fiber Architercture


以上所述就是小编给大家介绍的《基于React 源码深入浅出setState:深度刨析updater的结构和原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Design Patterns—深入淺出設計模式

Head First Design Patterns—深入淺出設計模式

天瓏

寫應用程式時需要依照需求預先規劃、設計,而設計模式累積了前人的經歷,經由四人幫彙整出一系列的設計模式,以利後人可以套用。本書集合四人幫的23個模式(十幾年前的事)外加這十幾年來新增的一些模式,作者群以詼諧、幽默、圖文並茂、打破傳統著書的方式,由淺入深地詳解了設計模式的精神及重點。全書全部以當紅的 Java 程式語言為範例。 本書特點: * 全世界第二本書......一起来看看 《Head First Design Patterns—深入淺出設計模式》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具