// ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
// 作为参数让不同的平台来控制属性更新的方式
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function(partialState, callback) {
// 仅仅调用了 updater 上的方法 updater 是初始化的第三个参数的实例属性,跟平台相关
// 这里的 this 即对应组件(fiber 节点)
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
// 找到 FiberRoot,并更新 FiberTree 上所有 Fiber 节点的expirationTime
const root = scheduleWorkToRoot(fiber, expirationTime);
// 没有 FiberRoot 返回
if (root === null) {
return;
}
if (
!isWorking && // 没有执行渲染, isWorking 表示有任务正在进行中
nextRenderExpirationTime !== NoWork && // 任务是个异步的,执行到一半了,把执行权交还给浏览器执行
expirationTime < nextRenderExpirationTime // 新的任务优先级高于现在的任务
) {
// 新的高优先级的任务打断了老的低优先级任务,优先执行高优先级的任务
// This is an interruption. (Used for performance tracking.)
// 用来记录
interruptedBy = fiber;
// 重置并回退之前更新过的state
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
// 没有正在工作, isWorking 和 isCommitting 对应两个不同阶段,isWorking包含isCommitting 和 rendering, 简单说就是要么处于没有 work 的状态,要么只能在 commit 阶段
!isWorking ||
// 正在提交,也就是更新dom 树的渲染阶段
isCommitting ||
// ...unless this is a different root than the one we're rendering.
// 不同的 root,一般不存在不同
nextRoot !== root
) {
// 调用了 markPendingPriorityLevel 后 rootExpirationTime 不一定是传入进来的 expirationTime
const rootExpirationTime = root.expirationTime;
// 开始调度
requestWork(root, rootExpirationTime);
}
// componentWillUpdate、componentDidUpdate 函数里调用 setState,死循环。。
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.',
);
}
}
function resetStack() {
// nextUnitOfWork 是用来记录下一个要更新的节点
if (nextUnitOfWork !== null) {
// 向上找被打断的任务
let interruptedWork = nextUnitOfWork.return;
while (interruptedWork !== null) {
// 退回状态,因为更高优先级的任务也是从头更新的,避免更新过的组件 state 错乱
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
// 重置初始值,进行新的优先级任务更新
nextRoot = null;
nextRenderExpirationTime = NoWork;
nextLatestAbsoluteTimeoutMs = -1;
nextRenderDidError = false;
nextUnitOfWork = null;
}
requestWork
!isWorking && isCommitting 正在提交也就是更新dom树的渲染阶段执行
将 FiberRoot 节点加入到 root 调度队列中
判断是否批量更新
根据 expirationTime 的类型判断调度的类型
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 将当前 FiberRoot 优先级设置为最高
addRootToSchedule(root, expirationTime);
// isRendering表示调度已经执行了,循环开始了,render 阶段不需要重新 requestWork 了
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
// 批量处理相关
// 调用 setState 时在 enqueueUpdates 前 batchedUpdates 会把 isBatchingUpdates 设置成 true, 和事件系统有关
// 为 true 则为调用 performSyncWork,则没有调度更新
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, true);
}
return;
}
// TODO: Get rid of Sync and use current time?
// 同步更新
if (expirationTime === Sync) {
performSyncWork();
} else {
// 异步调度,独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
addRootToSchedule
判断当前 FiberRoot 是否调度过,单个或多个 FiberRoot 构建成单向链表结构
如果调度过,设置当前任务优先级为最高优先级
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
// Add the root to the schedule.
// Check if this root is already part of the schedule.
// 首次渲染 root 没有进入调度
if (root.nextScheduledRoot === null) {
// This root is not already scheduled. Add it.
root.expirationTime = expirationTime;
// 只有一个 root 的情况
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
} else {
// 多个 root 构建单链表结构
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
}
} else {
// This root is already scheduled, but its priority may have increased.
// 传入的 FiberRoot 已经进入过调度
const remainingExpirationTime = root.expirationTime;
if (
remainingExpirationTime === NoWork ||
// root的 expirationTime 优先级低
expirationTime < remainingExpirationTime
) {
// Update the priority.
// 把当前 root 的优先级设置为当前优先级最高的
root.expirationTime = expirationTime;
}
}
}
function dispatchInteractiveEvent(topLevelType, nativeEvent) {
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}
function dispatchEvent(topLevelType, nativeEvent) {
if (!_enabled) {
return;
}
var nativeEventTarget = getEventTarget(nativeEvent);
var targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) {
// If we get an event (ex: img onload) before committing that
// component's mount, ignore it for now (that is, treat it as if it was an
// event on a non-React tree). We might also consider queueing events and
// dispatching them after the mount.
targetInst = null;
}
var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
try {
// Event queue being processed in the same cycle allows
// preventDefault.
batchedUpdates(handleTopLevel, bookKeeping);
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
}
function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
if (isBatchingInteractiveUpdates) {
return fn(a, b);
}
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
// this event.
if (
!isBatchingUpdates &&
!isRendering &&
lowestPriorityPendingInteractiveExpirationTime !== NoWork
) {
// Synchronously flush pending interactive updates.
performWork(lowestPriorityPendingInteractiveExpirationTime, null);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingInteractiveUpdates = true;
isBatchingUpdates = true;
try {
// 这里的 fn 为 dispatchEvent 自定义事件函数
return fn(a, b);
} finally {
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
}
}
}
setState 和 batchUpdates
setState 和 forceUpdate
来源
Component
组件原型上的方法,他们调用的enqueueSetState
和enqueueForceUpdate
都是来自不同平台创建的updater
对象上的,运行在浏览器中的updater
对象来自 ReactFiberClassComponent.js 里的classComponentUpdater
对象上。classComponentUpdater
在
beginWork
->updateClassComponent
中constructClassInstance
创建组件instance
时将classComponentUpdater
挂载到instance.updater
上enqueueSetState
,enqueueForceUpdate
几乎是相同的, 其中enqueueForceUpdate
只是将update
对象的tag
设置成了ForceUpdate
enqueueSetState
方法的执行的操作和updateContainer
几乎一模一样scheduleWork 开始调度
核心功能
scheduleWork
每一次进入调度队列的只有 FiberRoot 对象, 更新也是从 FiberRoot 对象上开始的。
scheduleWorkToRoot
resetStack
requestWork
addRootToSchedule
batchUpdates 批量更新
setState 调用
demo
涉及到更改 isBatchingUpdates 变量的两个主要函数, events模块都引用了这两个函数
function dispatchEvent(topLevelType, nativeEvent) { if (!_enabled) { return; }
var nativeEventTarget = getEventTarget(nativeEvent); var targetInst = getClosestInstanceFromNode(nativeEventTarget); if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) { // If we get an event (ex: img onload) before committing that // component's mount, ignore it for now (that is, treat it as if it was an // event on a non-React tree). We might also consider queueing events and // dispatching them after the mount. targetInst = null; }
var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);
try { // Event queue being processed in the same cycle allows //
preventDefault
. batchedUpdates(handleTopLevel, bookKeeping); } finally { releaseTopLevelCallbackBookKeeping(bookKeeping); } }function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R { if (isBatchingInteractiveUpdates) { return fn(a, b); } // If there are any pending interactive updates, synchronously flush them. // This needs to happen before we read any handlers, because the effect of // the previous event may influence which handlers are called during // this event. if ( !isBatchingUpdates && !isRendering && lowestPriorityPendingInteractiveExpirationTime !== NoWork ) { // Synchronously flush pending interactive updates. performWork(lowestPriorityPendingInteractiveExpirationTime, null); lowestPriorityPendingInteractiveExpirationTime = NoWork; } const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates; const previousIsBatchingUpdates = isBatchingUpdates; isBatchingInteractiveUpdates = true; isBatchingUpdates = true; try { // 这里的 fn 为 dispatchEvent 自定义事件函数 return fn(a, b); } finally { isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates; isBatchingUpdates = previousIsBatchingUpdates; if (!isBatchingUpdates && !isRendering) { performSyncWork(); } } }
方法一: 直接调用setState
React 的点击事件执行的是 React 包装过的 Event,执行栈:
dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1
这个函数里会将 isBatchingUpdates 置为 true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates 的值(false), 执行 try 里的 fn,执行栈里推入dispatchEvent -> batchedUpdates -> batchedUpdates$1
这个函数里也会将 isBatchingUpdates 置为true, 并且用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1
函数里try { fn } finally { ... }, 这里的 fn 就是setState了,执行栈直接推入enqueueSetState -> scheduleWork -> requestWork
此时 isBatchingUpdates 为 true, 因此每次调用setState 都不会执行 performSyncWork(),最后finally 里 判断!isBatchingUpdates && !isRendering
为false,不执行 performSyncWork(), 出栈到interactiveUpdates$1
函数里的try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断!isBatchingUpdates && !isRendering
为真(注意这个函数里的previousIsBatchingUpdates为false),因此执行 performSyncWork() 执行更新多次的 setState 在 enqueueUpdates 函数中,fiber 对象的 baseState 仍然是 0, 但是 fiber 对象上的 updateQueue 更新队列上已经记录好了多次 update 对象将要更新 state 的 payload。
函数调用栈
upDateQueue
方法二: 使用setTimeout, setTimeout(() => (this.setState(xxx))),或者 Promise.then(() => { this.setState({xxx})})
React 的点击事件执行的是 React 包装过的 Event,执行栈:
dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1
这个函数里会将 isBatchingUpdates 置为true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates 的值(false), 执行 try 里的 fn,执行栈里推入dispatchEvent -> batchedUpdates -> batchedUpdates$1
这个函数里也会将 isBatchingUpdates 置为 true, 并且 用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1
函数里 try { fn } finally { ... } 这里的 fn 就是setTimeout 为异步(到下一次事件循环才执行),判断 !isBatchingUpdates && !isRendering 为 false,不执行 performSyncWork, 出栈到interactiveUpdates$1
函数里的 try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断!isBatchingUpdates && !isRendering
为真(注意这个函数里的 previousIsBatchingUpdates 为 false),执行 performSyncWork() ,回到组件回调函数里,到这里才走完自定义事件的回调。setTimeout 回调属于 marcoTask,它的执行在下一次任务队列开始,在执行 setTimeout 的回调,直接调用 setState,执行栈会推入
enqueueSetState -> scheduleWork -> requestWork
,此时 isBatchingUpdates 为 false(等到setTimeout 执行时 isBatchingUpdates 赋值为 false 了), 因此每走一次 setState 都会走一次 performSyncWork() 将 state 更新。函数调用栈
baseState
在每次执行完上一次 performWork 后都会更新为最新的 state方法三: 在setTimeout里主动调用 batchUpdates
React 的点击事件执行的是 React 包装过的 Event,执行栈:
dispatchInteractiveEvent -> interactiveUpdates -> interactiveUpdates$1
这个函数里会将 isBatchingUpdates 置为 true, 并且 previousIsBatchingUpdates 记录首次加载的 isBatchingUpdates的值(false), 执行 try 里的 fn,执行栈里推入dispatchEvent -> batchedUpdates -> batchedUpdates$1
这个函数里也会将 isBatchingUpdates 置为true, 并且 用previousIsBatchingUpdates 记录原 isBatchingUpdates 值(此时为 true );batchedUpdates$1 函数里try { fn } finally { ... } 这里的 fn 就是setTimeout为异步(到下一次事件循环才执行),判断 !isBatchingUpdates && !isRendering 为false,不执行 performSyncWork, 出栈到interactiveUpdates$1
函数里的try {...} finally { ... } 重新将 isBatchingUpdates 赋值为false,判断!isBatchingUpdates && !isRendering
为真(注意这个函数里的 previousIsBatchingUpdates 为 false),因此执行 performSyncWork() ,回到组件回调函数里,到这里才走完自定义事件的回调。由于 setTimeout() 函数里又使用 batchedUpdates 函数主动包裹一次,因此执行栈里推入setTimeout的回调, 将 isBatchingUpdates 置为true, 并且 previousIsBatchingUpdates 记录的 原isBatchingUpdates的值(false), 执行 fn 即调用setState,执行栈会推入
enqueueSetState -> scheduleWork -> requestWork
, 在requestWork里判断 isBatchingUpdates 为 true,直接 return 不执行 performSyncWork 函数,执行完三次 setState后,fiber节点上的 updateQueue 的baseState还是 0 ,fisrtUpdate为单链表结构记录着每次setState的payload ;执行完三次 setState 后回到batchedUpdates$1
函数里try {...} finally { ... } 将 isBatchingUpdates 重新赋值为 false,再走performSyncWork() 将 lastUpdate 的 payload 更新到页面上函数调用栈
upDateQueue
和方法一直接调用一样方法四: 原生事件绑定
原生事件未走自定义事件,因此没有走
dispatchInteractiveEvent -> interactiveUpdates
这个流程,isBatchingUpdates
始终为false
,因此每次setState
都会走一次performSyncWork()
函数调用栈
总结
setState
是同步还是异步setState
方法调用时本身是同步的,但调用setState
不表示state
是立即更新的,state
的更新是根据我们执行环境的上下文来判断的如果处于批量更新的情况下
state
就不是立即更新的,如果不处于批量更新情况下有可能立即更新有可能 是因为现在有
asyncMode
异步渲染的情况,state
也不是立即更新的,需要进入异步调度的过程。上一篇 下一篇