内容简介:作者 : 墨成 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); }复制代码
- 前三个变量 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.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 )
简单的概括下:在任何时候,一个组件的实例最多应对两个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 涉及调度器的知识体系,后续学习完成补充着部分的欠缺 。
参考资料 :
以上所述就是小编给大家介绍的《基于React 源码深入浅出setState:深度刨析updater的结构和原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ThreadLocal源码深度剖析
- SnapHelper源码深度解析
- Injection源码深度解析
- 一文深度剖析 Axios 源码
- 深度解读 ReentrantLock 底层源码
- Golang channel源码深度剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
打火机与公主裙·荒草园
Twentine / 青岛出版社 / 2017-3 / 36.00元
“如果人临死前真有走马灯这个环节,她大概会是我这辈子见的最后一人。” 从青涩的校园时代里一抹明亮的金,到厮杀的职场中那化不开的黑,李峋就像荒芜之地的一株野草,受到再大的挫折依然固执地生长。 如果说朱韵从前的生活一直维持着表面的顺风顺水,平静安和,那李峋的出现则打破了这一切。他是她生命中第一次,也是唯一一次的冒险。 在外人眼里李峋嚣张而轻蔑,只有朱韵懂得他心中那片自留地,自愿成为孤......一起来看看 《打火机与公主裙·荒草园》 这本书的介绍吧!