作者 : 墨成 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 涉及调度器的知识体系,后续学习完成补充着部分的欠缺 。
参考资料 :