首先我们以一个简单的示例来分析其大概流程
示例如下
class SetTest extends React.Component {
constructor (props) {
super(props)
this.state = {
name: 'ali',
age: 18
}
}
chgValue = () => {
this.setState({
name: 'alinew' + Date.now()
})
this.setState({
age: 16
})
}
render() {
return (
<div>
<span>name is {this.state.name}</span><br/>
<span>age is {this.state.age}</span>
<p>
<button onClick={this.chgValue}>click me</button>
</p>
</div>
)
}
}
export default SetTest
在该示例值只是通过click来触发setState。
其函数的调用流程大体如下所示
setState.png
setState.png
有图可知setState分为两个阶段
- 该示例中通过click触发的setState为异步任务, 所以在刚开始的时候会将一系列的update挂载到其对应fiber的updateQueue链上。接着会创建一个任务并将该任务推入 schedule任务调度流中。 等待浏览器空余时间执行其中的work任务
- 通过flushSyncCallbackQueue执行当前需要执行的任务, 进入workLoopSync循环之中。 开始beginWork后执行的仍旧是reconcile调和过程、commit提交副作用阶段
enqueueSetState
该方法主要功能有
- enqueueUpdate将需要更新的对象放入fiber的更新队列中
- 进入任务调度阶段, 将需要执行的任务放入任务队列中
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
var currentTime = requestCurrentTimeForUpdate();
var suspenseConfig = requestCurrentSuspenseConfig();
var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig);
var update = createUpdate(expirationTime, suspenseConfig);
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
}
ensureRootIsScheduled
- 首先判断当前时刻有是否需要执行的work, 如果没有则返回
- 如果当前有需要同步执行的任务放入scheduleSyncCallback
- 如果当然任务为异步的话, 则按照优先级放入异步队列中
function ensureRootIsScheduled(root) {
var lastExpiredTime = root.lastExpiredTime;
if (lastExpiredTime !== NoWork) {
// Special case: Expired work should flush synchronously.
root.callbackExpirationTime = Sync;
root.callbackPriority = ImmediatePriority;
root.callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
return;
}
var expirationTime = getNextRootExpirationTimeToWorkOn(root);
var existingCallbackNode = root.callbackNode;
if (expirationTime === NoWork) {
// There's nothing to work on.
if (existingCallbackNode !== null) {
root.callbackNode = null;
root.callbackExpirationTime = NoWork;
root.callbackPriority = NoPriority;
}
return;
} // TODO: If this is an update, we already read the current time. Pass the
// time as an argument.
var currentTime = requestCurrentTimeForUpdate();
var priorityLevel = inferPriorityFromExpirationTime(currentTime, expirationTime); // If there's an existing render task, confirm it has the correct priority and
// expiration time. Otherwise, we'll cancel it and schedule a new one.
...
if (expirationTime === Sync) {
// Sync React callbacks are scheduled on a special internal queue
callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
callbackNode = scheduleCallback(priorityLevel, performConcurrentWorkOnRoot.bind(null, root), // Compute a task timeout based on the expiration time. This also affects
// ordering because tasks are processed in timeout order.
{
timeout: expirationTimeToMs(expirationTime) - now()
});
}
root.callbackNode = callbackNode;
}
scheduleSyncCallback
对于同步执行的callback函数将其放入syncQueue, 同时在这里我们可以看到flushSyncCallbackQueueImpl作为回调函数传入到Scheduler_scheduleCallback方法中,以供后续调用
function scheduleSyncCallback(callback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback]; // Flush the queue in the next tick, at the earliest.
immediateQueueCallbackNode = Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueueImpl);
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
return fakeCallbackNode;
}
flushSyncCallbackQueueImpl
通过该方法可以清楚的看出它取出syncQueue队列并按照ImmediatePriority(立即执行)的优先级执行其中的队列任务。
以该示例来说它的队列任务为[performSyncWorkOnRoot]
会进入的同步work的调度中。
function flushSyncCallbackQueueImpl() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrancy.
isFlushingSyncQueue = true;
var i = 0;
try {
var _isSync = true;
var queue = syncQueue;
runWithPriority$1(ImmediatePriority, function () {
for (; i < queue.length; i++) {
var callback = queue[I];
do {
callback = callback(_isSync);
} while (callback !== null);
}
});
syncQueue = null;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
} // Resume flushing in the next tick
Scheduler_scheduleCallback(Scheduler_ImmediatePriority, flushSyncCallbackQueue);
throw error;
} finally {
isFlushingSyncQueue = false;
}
}
}
updateClassComponent
在setState更新阶段, updateClassInstance
对该组件实例属性进行更新, 如执行updateQueue链更新其对应的state变更的值。
state更新完成后, 需要生成新的虚拟DOM这一系列的过程在finishClassComponent
。
render完成后会返回组件的根节点fiber作为下一次迭代的workInProgress
function updateClassComponent(current, workInProgress, Component, nextProps, renderExpirationTime) {
...
if (instance === null) {
if (current !== null) {
// A class component without an instance only mounts if it suspended
// inside a non-concurrent tree, in an inconsistent state. We want to
// treat it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
} // In the initial pass we might need to construct the instance.
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
} else {
shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderExpirationTime);
}
var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);
...
return nextUnitOfWork;
}
updateHostComponent
再一次的workInProgress进入的组件根fiber中, 此时会触发updateHostComponent函数。
通过该方法进入到 reconcileChildren调和阶段, 此后的阶段与上一节渲染分析一致在此不做赘述。
function updateHostComponent(current, workInProgress, renderExpirationTime) {
pushHostContext(workInProgress);
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
var type = workInProgress.type;
var nextProps = workInProgress.pendingProps;
var prevProps = current !== null ? current.memoizedProps : null;
var nextChildren = nextProps.children;
var isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.effectTag |= ContentReset;
}
markRef(current, workInProgress); // Check the host config to see if the children are offscreen/hidden.
if (workInProgress.mode & ConcurrentMode && renderExpirationTime !== Never && shouldDeprioritizeSubtree(type, nextProps)) {
{
markSpawnedWork(Never);
} // Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
return null;
}
reconcileChildren(current, workInProgress, nextChildren, renderExpirationTime);
return workInProgress.child;
}
由该示例可知当前div下有4个孩子节点,workLoop之后其中的span1、 span2会涉及到变更。
最终会生成两个effect副作用, 在commit阶段进行DOM的变更处理。 两个effect的effectTag为4即对应commitTextUpdate的更新
网友评论