React版本
16.8.6
Component.prototype.setState
react/packages/react/src/ReactBaseClasses.js
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');
};
this.updater.enqueueSetState
react/packages/reactreconciler/src/ReactFiberClassComponent.js
如果搜索这个函数名的话,你会发现有四个地方,三个是定义,一个是使用,另外两个定义的地方分别是用来测试和报错
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(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;
}
//flushPassiveEffects和scheduleWork都会根据以下路径的boolean常量(false)进行不同的函数赋值
//应该是控制新旧版本逻辑的代码,可能未来返回true,使用新的调度逻辑
//react/packages/shared/ReactFeatureFlags.js
flushPassiveEffects(); //好像没什么意义,最终返回了一个boolean,也没人承接
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
}
getInstance
react/packages/shared/ReactInstanceMap.js
此处是getInstance,使用了as关键字进行了重命名
export function get(key) {
return key._reactInternalFiber;
}
createUpdate
react/packages/react-reconciler/src/ReactUpdateQueue.js
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
return {
expirationTime: expirationTime,
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null,
};
}
enqueueUpdate
react/packages/react-reconciler/src/ReactUpdateQueue.js
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
const alternate = fiber.alternate; //获取fiber替身,就是即将要更新的fiber对象
let queue1;
let queue2;
//下面是对queue1、queue2的空值处理
if (alternate === null) { //首次更新
queue1 = fiber.updateQueue;
queue2 = null;
if (queue1 === null) {
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
}
} else { //非首次更新
queue1 = fiber.updateQueue;
queue2 = alternate.updateQueue;
if (queue1 === null) {
if (queue2 === null) {
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
queue2 = alternate.updateQueue = createUpdateQueue(
alternate.memoizedState,
);
} else {
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
} else {
if (queue2 === null) {
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
} else {
// Both owners have an update queue.
}
}
}
//下面添加队列逻辑
if (queue2 === null || queue1 === queue2) { //只有一种queue的情况
appendUpdateToQueue(queue1, update);
} else { //当有两个queue时合并,因为我们不想让相同的queue多次添加
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
//只有一种lastUpdate的情况,将lastUpdate添加到两个queue
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);
//修正queue2.lastUpdate指向
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;
}
}
}
createUpdateQueue
react/packages/react-reconciler/src/ReactUpdateQueue.js
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
baseState,
firstUpdate: null, //链表头
lastUpdate: null, //链表尾
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
appendUpdateToQueue
react/packages/react-reconciler/src/ReactUpdateQueue.js
function appendUpdateToQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>,
) {
//链表插入逻辑
if (queue.lastUpdate === null) {
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
scheduleWork
react/packages/react-reconciler/src/ReactFiberScheduler.old.js
function scheduleWork (fiber: Fiber, expirationTime: ExpirationTime) {
const root = scheduleWorkToRoot(fiber, expirationTime); //获取 fiber root
if (root === null) {
if (__DEV__) {
switch (fiber.tag) {
case ClassComponent:
warnAboutUpdateOnUnmounted(fiber, true);
break;
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
warnAboutUpdateOnUnmounted(fiber, false);
break;
}
}
return;
}
if (
!isWorking && //如果不处于任务状态
nextRenderExpirationTime !== NoWork && //从词义可以看出来:下一个渲染过期时间段有任务
expirationTime > nextRenderExpirationTime //比较优先级
) {
interruptedBy = fiber; //记录打断者
resetStack(); //重置fiber栈
}
markPendingPriorityLevel(root, expirationTime); //标记等待任务优先级
if (
// 如果我们在执行任务状态,我们不需要为了更新调度这个fiber任务,因为我们会在退出之前执行它,除非两者(正在渲染的和等待的)的fiber root不相同
!isWorking ||
isCommitting ||
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime); //请求执行任务
}
//触发无限循环逻辑警告
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside ' +
'componentWillUpdate or componentDidUpdate. React limits ' +
'the number of nested updates to prevent infinite loops.',
);
}
if (__DEV__) {
if (
isInPassiveEffectDEV &&
nestedPassiveEffectCountDEV > NESTED_PASSIVE_UPDATE_LIMIT
) {
nestedPassiveEffectCountDEV = 0;
warning(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component calls setState inside useEffect, but ' +
"useEffect either doesn't have a dependency array, or " +
'one of the dependencies changes on every render.',
);
}
}
}
scheduleWorkToRoot
react/packages/react-reconciler/src/ReactFiberScheduler.old.js
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
recordScheduleUpdate(); //记录调度器的状态
if (__DEV__) {
if (fiber.tag === ClassComponent) {
const instance = fiber.stateNode;
warnAboutInvalidUpdates(instance);
}
}
// expirationTime 越大说明优先级越高
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
let alternate = fiber.alternate;
if (alternate !== null && alternate.expirationTime < expirationTime) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
// 获取 fiber 的父节点
let node = fiber.return;
let root = null;
// 判断这时候 fiber 是否为 Root Fiber
if (node === null && fiber.tag === HostRoot) {
// 取出 Fiber Root
root = fiber.stateNode;
} else {
while (node !== null) { //Root Fiber 是没有 return 属性的
alternate = node.alternate;
//调整优先级
if (node.childExpirationTime < expirationTime) {
node.childExpirationTime = expirationTime;
if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
// 这部分内容是 React Profiler 相关的,DevTool 相关的内容
// 可以方便找出你的 React 应用的性能瓶颈
if (enableSchedulerTracing) {
if (root !== null) {
const interactions = __interactionsRef.current;
if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap;
const pendingInteractions = pendingInteractionMap.get(expirationTime);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}
pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(expirationTime, new Set(interactions));
// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(
expirationTime,
root.interactionThreadID,
);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
}
return root;
}
recordScheduleUpdate
react/packages/react-reconciler/src/ReactDebugFiberPerf.js
export function recordScheduleUpdate(): void {
if (enableUserTimingAPI) {
if (isCommitting) {
hasScheduledUpdateInCurrentCommit = true;
}
if (
currentPhase !== null &&
currentPhase !== 'componentWillMount' &&
currentPhase !== 'componentWillReceiveProps'
) {
hasScheduledUpdateInCurrentPhase = true;
}
}
}
resetStack
react/packages/react-reconciler/src/ReactFiberScheduler.old.js
function resetStack() {
if (nextUnitOfWork !== null) {
let interruptedWork = nextUnitOfWork.return; //向上查找节点
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork); //还原已经改变的节点,这里不再展开,是一系列的还原逻辑
interruptedWork = interruptedWork.return;
}
}
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
checkThatStackIsEmpty();
}
// 重置变量
nextRoot = null;
nextRenderExpirationTime = NoWork;
nextLatestAbsoluteTimeoutMs = -1;
nextRenderDidError = false;
nextUnitOfWork = null;
}
requestWork
react/packages/react-reconciler/src/ReactFiberScheduler.old.js
这里是主要内容
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 将fiber root加入调度中
addRootToSchedule(root, expirationTime);
if (isRendering) {
//阻止重复渲染,剩余的任务将会在当前渲染阶段结束调度
return;
}
// 以下判断,执行的更新任务会包一层batchedUpdates函数,执行时改变下面判断变量的值,进而判断任务是否需要批量更新
// 判断是否需要批量更新
if (isBatchingUpdates) {
// 判断是否不需要批量更新
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
// 除非我们还未在里面批量更新,这时我们应该立即批量更新它,说实话我不太懂这种是什么情况
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return; //这里是为什么我们的setState没有立即更新状态的原因
}
// 不需要批量更新的逻辑
// TODO: Get rid of Sync and use current time?
// 以上TODO还未实现
// 判断优先级是同步还是异步,异步的话需要调度
if (expirationTime === Sync) {
performSyncWork(); //执行同步任务
} else {
// 因为setTimeOut等类似函数属于异步,所以执行这个分支的函数
// 函数核心是实现了 requestIdleCallback 的 polyfill 版本
// 因为这个函数浏览器的兼容性很差
// 具体作用可以查看 MDN 文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback
// 这个函数可以让浏览器空闲时期依次调用函数,这就可以让开发者在主事件循环中执行后台或低优先级的任务,
// 而且不会对像动画和用户交互这样延迟敏感的事件产生影响
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
scheduleCallbackWithExpirationTime
react/packages/react-reconciler/src/ReactFiberScheduler.old.js
function scheduleCallbackWithExpirationTime(
root: FiberRoot,
expirationTime: ExpirationTime,
) {
// 判断上一个 callback 是否执行完毕
if (callbackExpirationTime !== NoWork) {
if (expirationTime < callbackExpirationTime) {
return; // 当前任务如果优先级小于上个任务就退出
} else {
if (callbackID !== null) {
cancelCallback(callbackID); // 否则的话就取消上个 callback
}
}
// The request callback timer is already running. Don't start a new one.
} else {
// 没有需要执行的上一个 callback,开始定时器,这个函数用于 devtool
startRequestCallbackTimer();
}
callbackExpirationTime = expirationTime;
// 当前 performance.now() 和程序刚执行时的 performance.now() 相减
const currentMs = now() - originalStartTimeMs;
const expirationTimeMs = expirationTimeToMs(expirationTime);
// 毫秒相减,优先级越大,算出来的值越小,可以自己模拟一下
// 这个值在后面会和 performance.now() 相加再算出一个 expirationTime
const timeout = expirationTimeMs - currentMs;
// 获取优先级,这个优先级有五个,但是这个优先级其实在 scheduleCallback 函数中压根没用到
const priorityLevel = getCurrentPriorityLevel();
callbackID = scheduleCallback(priorityLevel, performAsyncWork, {timeout});
}
总结
可以看出来,setState本身乃至本身做的事情都是同步的,只是将任务加入队列,并根据react类型判断是否需要批量更新,而使用setTimeout这种api会判断为异步类型导致立即执行任务。