著有《React18 设计原理》《javascript地月星》等多个专栏。 欢迎关注。
创作不易,内容有帮助记得 ❤️点赞,⭐️收藏 ,🔥评论 ~
本文全部都是原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解 ~
推荐指数(值得一读):⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
github 👉github.com/chd666233/b...
初始化 :执行了组件函数的时候,每个 useState 都会创建一个 hook、queue。
事件 :setXXX 更新值到对应的 update -> queue -> hook。
触发协调 :再次执行组件函数创建组件、新 hook。旧 hook 的值同步到新 hook。 
1. 初始化创建组件、创建组件 hook 和 queue
在组件创建的时候,每个 useXXX 都会调用 mountState 函数。每个 useXXX 都是一个 hook,创建 hook。
js
在组件创建的时候,每隔一个useXXX都会调用这个函数
function mountState(initialState) {
//⭐️
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
hook.queue = queue;
var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}
js
function mountWorkInProgressHook() {
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
2. 事件中触发 setXXX
js
function dispatchSetState(fiber, queue, action) {
...
var lane = requestUpdateLane(fiber);
var update = {
lane: lane,
action: action,
hasEagerState: false,
eagerState: null,
next: null
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
var alternate = fiber.alternate;
...
var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
var eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane);
}
创建一个循环队列。update是一个队列,通过 update.next 指向下一个节点。update 是每一次 state 改变的值,例如输入事件每次的改变:j、jk、jkl...。
没有一直积累 update.next,新的协调会清除 queue.interleaved = null; ,再一次调度会发生 interleaved === null,于是重新积累队列。也就是说,协调后就丢弃了早期的 state 值。这样做节省内存。
js
// 创建、维护一个循环队列。update是一个队列,通过update.next指向下一个节点。
function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
//上次的update。就是末尾节点。
var interleaved = queue.interleaved;
if (interleaved === null) {
update.next = update;
pushConcurrentUpdateQueue(queue);
} else {
// 新旧尾节点的交接,设置新尾节点的头节点,然后把旧尾节点的末尾设置为新尾节点
// update是一个队列,通过update.next指向下一个节点。
//interleaved.next:上次update的next就是头节点。
update.next = interleaved.next;
//上次尾节点的next更新为这次update
interleaved.next = update;
}
//queue.interleaved保存尾节点。
queue.interleaved = update;
return markUpdateLaneFromFiberToRoot(fiber, lane);
}
//把queue收集到concurrentQueues数组
var concurrentQueues = null;
function pushConcurrentUpdateQueue(queue) {
if (concurrentQueues === null) {
concurrentQueues = [queue];
} else {
concurrentQueues.push(queue);
}
}
3. 协调 循环 Fiber 树前
js
//这是在遍历 Fiber 树前前调用的。
function finishQueueingConcurrentUpdates() {
if (concurrentQueues !== null) {
for (var i = 0; i < concurrentQueues.length; i++) {
// 取queue
var queue = concurrentQueues[i];
//把update 从 queue.interleaved 迁移到 queue.pending
var lastInterleavedUpdate = queue.interleaved;
if (lastInterleavedUpdate !== null) {
// 把queue.interleaved清除
queue.interleaved = null;
var firstInterleavedUpdate = lastInterleavedUpdate.next;
var lastPendingUpdate = queue.pending;
if (lastPendingUpdate !== null) {
var firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = firstInterleavedUpdate;
lastInterleavedUpdate.next = firstPendingUpdate;
}
// 迁移
queue.pending = lastInterleavedUpdate;
}
}
concurrentQueues = null;
}
}
4. 协调 循环 Fiber 树,遍历到一个组件节点,重新创建组件的时候
在组件再次创建的时候,每个 useXXX 都会调用 updateState(不再是 mountState函数)。
重新创建了 hook 链表,需要把 state 变更后的最新值(不再是初始值),同步到新 hook,通过 return [hook.memoizedState, dispatch] 返回给组件。
4.1. hook 规则 为什么不能在条件语句中使用 hook
新 hook 一开始没有最新的值,要从旧 hook 同步过来。这就是为什么不能在条件语句中使用 hook --------- 新旧链表不一致导致错误!
js
创建组件,按照组件开头hook的顺序调用updateState,每个updateState都会取一个hook,下一个updateState取hooks.next
function updateState(initialState) {
return updateReducer(basicStateReducer);
}
function updateReducer(reducer, initialArg, init) {
//⭐️updateWorkInProgressHook 将取到旧 hook 链表的头节点,current.memoizedState。
// updateWorkInProgressHook这里取hook
var hook = updateWorkInProgressHook();
var queue = hook.queue;
...
var dispatch = queue.dispatch;
//返回给const [xxx,setXXX] = useXXX();
return [hook.memoizedState, dispatch];
}
js
function updateWorkInProgressHook() {
var nextCurrentHook;
//旧hook链表
if (currentHook === null) {
var current = currentlyRenderingFiber$1.alternate;
if (current !== null) {
//hook头
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
//下一个hook
nextCurrentHook = currentHook.next;
}
var nextWorkInProgressHook;
//新hook链表
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
//新hooks链表已经有这个hook了
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
// 克隆旧hooks到新hooks
if (nextCurrentHook === null) {
throw new Error('Rendered more hooks than during the previous render.');
}
currentHook = nextCurrentHook;
var newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null
};
if (workInProgressHook === null) {
// This is the first hook in the list.
//保存到新Fiber memoizedState
currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
结语
新建组件重新创建了 hooks 链表,为了把当前状态同步到新 hook 不得不"查找"对应的旧 hook。