demo
函数组件触发更新的过程
demo
export default function App() {
const [c, setC] = useState(0)
return (
<div onClick={() =>{
setC(c + 1)
}}>
{c}
</div>
);
}
dispatchAction
onClick调用setC时会触发函数组件的更新,此时调用的实际上是dispatchAction(创建hook时通过dispatchAction.bind绑定到fiber,获取setC)。
触发更新后先创建一个update对象
var update = {
lane: lane,
action: action,
eagerReducer: null,
eagerState: null,
next: null
};
- action就是我们传入的state,第一次调用是1
- eagerReducer会复用上一次reducer
- eagerState就是这一次要更新的state
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
这一部分将update对象挂载到queue.pending上(queue是hook对象的一部分),挂载前检测一下pending上有没有其他的update。
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action);
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
这一部分获取新的state(eagerState),复用上一次的reducer,然后通过scheduleUpdateOnFiber调度更新
总结一下这一部分的任务
- 创建包含了state的update对象
- 将update对象挂载到queue.pending.next上
- 用更新update的eagerState和eagerReducer
scheduleUpdateOnFiber
这一部分开始调度我们的更新
markUpdateLaneFromFiberToRoot
这个函数用来合并lane
首先合并update的lane和当前fiber的lane
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
lane是一个二进制数据,mergeLanes通过或运算进行合并
function mergeLanes(a, b) {
return a | b;
}
接下来将lane合并到每个parent.childLanes上,就像下面这样
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
} else {
{
if ((parent.flags & (Placement | Hydrating)) !== NoFlags) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
}
}
node = parent;
parent = parent.return;
}
var priorityLevel = getCurrentPriorityLevel()
获取当前任务的优先级,用户触发的优先级是UserBlockingPriority(98),低于ImmediatePriority(99)
总结一下这一阶段的任务
- 将新的lane合并到fiber.lane上
- 将新的lane合并到每一层的parent.childLanes上
ensureRootIsScheduled -> Scheduler_scheduleCallback
ensureRootIsScheduled前面都是lane相关的操作,直接看Scheduler_scheduleCallback的调度过程(其实中间还有一个scheduleSyncCallback)。
var currentTime = exports.unstable_now();
var startTime;
先获取当前的时间,然后获取任务开始的时间,如果没有delay的话startTime=currentTime
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
//some code
case IdlePriority:
//some code
case LowPriority:
//some code
case NormalPriority:
default:
//some code
}
此时具有最高的优先级ImmediatePriority,timeout(任务可以延迟执行的时间) = -1,立刻执行
var expirationTime = startTime + timeout;
expirationTime获取任务的过期时间,就是任务开始时间 + 可以延迟的时间,此时timeout是-1,说明任务已经过期了,立刻执行
var newTask = {
id: taskIdCounter++,
callback: callback,
priorityLevel: priorityLevel,
startTime: startTime,
expirationTime: expirationTime,
sortIndex: -1
};
创建一个task对象,callback是flushSyncCallbackQueueImpl
if (startTime > currentTime) {
// some code
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
由于任务已经过期,直接进入else逻辑,React内部有两个queue,分别是taskQueue和timerQueue,都是heap,task存放的是过期的任务,要立即执行,timerQueue里面的任务还没有过期。这里直接把任务push到taskQueue中
requestHostCallback里面是和postMessage相关的函数
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null);
}
var channel = new MessageChannel();
var port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
也就是说调用port.postMessage后调用performWorkUntilDeadline启动循环,后面就是render了