博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于React 源码深入浅出setState:深度刨析updater的结构和原理
阅读量:6448 次
发布时间:2019-06-23

本文共 8653 字,大约阅读时间需要 28 分钟。

作者 : 墨成   React 版本 :16.4.1

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

在详细了解了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; 

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.jsexport type Update
= { expirationTime: ExpirationTime,//有效时间 tag: 0 | 1 | 2 | 3,//tag 1 2 3 4 其中之一 payload: any,//update的partialState callback: (() => mixed) | null,//setState的回调函数 next: Update
| null,//指向下一个 update的指针 nextEffect: Update
| null,//指向下一个effect(变化) };export type UpdateQueue
= { baseState: State,//就是Component初始化的state firstUpdate: Update
| null, lastUpdate: Update
| null, firstCapturedUpdate: Update
| null, lastCapturedUpdate: Update
| null, firstEffect: Update
| null, lastEffect: Update
| null, firstCapturedEffect: Update
| null, lastCapturedEffect: Update
| null,};复制代码

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

//以下注释仅对 ClassComponent有效(即你的组件是继承React.Component)export function enqueueUpdate
(fiber: Fiber, update: Update
) { // 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 ( 参考) 

简单的概括下:在任何时候,一个组件的实例最多应对两个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.

转载地址:http://gllwo.baihongyu.com/

你可能感兴趣的文章
《Android应用开发》——1.1节下载开发软件
查看>>
《贝叶斯思维:统计建模的Python学习法》——1.7 Monty Hall难题
查看>>
升级TCP协议使网速提升30%,中国受益明显
查看>>
Go 语言对 Android 原生应用开发的支持情况
查看>>
《沟通的技术——让交流、会议与演讲更有效》一1.1 一切尽在计划之中
查看>>
Firefox 44 浏览器内建更好的 SSL 错误指示器
查看>>
《数据科学:R语言实现》——2.9 使用twitteR
查看>>
《思科UCS服务器统一计算》一第2章 服务器架构2.1 处理器的演变
查看>>
微软概述 Islandwood 计划
查看>>
《CUDA C编程权威指南》——3.2节理解线程束执行的本质
查看>>
《深入理解Android》一导读
查看>>
linux查看登录用户及踢掉用户
查看>>
如何防止网站因改版导致权重下降?
查看>>
《伟大的计算原理》一大数据
查看>>
《UG NX8.0中文版完全自学手册》一导读
查看>>
Windows和Linux端rsync推拉同步时中文乱码解决方法
查看>>
《Spark核心技术与高级应用》——1.3节本章小结
查看>>
解决军哥lnmp一键包报错pycurl.so
查看>>
Google 搜索结果正式 AMP 化
查看>>
《树莓派Python编程入门与实战》——第2章 认识Raspbian Linux发行版 2.1 了解Linux...
查看>>