前言
这个是关于hook可能导致组件更新的最后一个,前面已经讲了关于能够更新的hook有useState ,useReducer ,useTransition ,useDeferredValue ,实际上useDeferredValue 是没有自己能够主动重新渲染的能力的,依赖于其他方法触发渲染,而其他三种是能够主动触发的;
阅读本章之前建议先读mountEffect,有一些源码是依赖于useEffect的。
源码解析
js
// 和useEffect执行时机一致,返回一个函数
const subscribe = (handleStoreChange)=>{
}
// 在createRoot模式下调用
const getSnapshot = () => {
}
// 针对与hydrateRoot模式调用
function getServerSnapshot() {
}
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
mount阶段
按照常规流程,调用HooksDispatcherOnMountInDEV.useSyncExternalstore 的方法,前置check检查方法,核心代码mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) ;
首先调用mountWorkInProgressHook方法,建立一个hook对象,
js
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
}
判断当前渲染的模式,是调用getSnapshot 方法还是getServerSnapshot 方法,调用两次*Snapshot方法,将两次返回值进行Object.is比较,如果不同会有个error提示;
js
const nextSnapshot = getSnapshot();
hook.memoizedState = nextSnapshot;
const inst = {
value: nextSnapshot,
getSnapshot: getSnapshot
}
hook.queue = inst
调用mountEffect方法,这个是useEffect核心方法,
js
function checkIfSnapshotChanged(inst) {
var latestGetSnapshot = inst.getSnapshot;
var prevValue = inst.value;
try {
var nextValue = latestGetSnapshot();
return !objectIs(prevValue, nextValue);
} catch (error) {
return true;
}
}
function subscribeToStore(fiber, inst, subscribe) {
var handleStoreChange = function () {
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceStoreRerender(fiber);
}
};
return subscribe(handleStoreChange);
}
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);
const Passive = 0b00000000000000100000000000;
fiber.flags |= Passive;
关于mountEffect的代码会放在mountEffect章节中讲解; 主要是创建一个hook对象,和调用pushEffect 方法,
本身也会调用一次pushEffect方法,
js
const HasEffect = 0b0001;
const Passive = 0b1000;
function updateStoreInstance<T>(
fiber,
inst,
nextSnapshot,
getSnapshot,
) {
inst.value = nextSnapshot;
inst.getSnapshot = getSnapshot;
if (checkIfSnapshotChanged(inst)) {
// Force a re-render.
forceStoreRerender(fiber);
}
}
pushEffect(
HasEffect | Passive, // flags状态
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
undefined,
null,
);
将nextSnapshot返回。
fiber.updateQueue的任务队列中会创建两个链表,一个是mountEffect创建的,一个是pushEffect创建的;
在执行useEffect时机时,参考commitRoot阶段,遍历updateQueue队列,首先执行mountEffect任务,会执行subscribe 方法,传入一个函数handleStoreChange , 然后执行pushEffect任务,执行updateStoreInstance函数,会再次调用getSnapshot方法,对比之前的值,决定是否调用forceStoreRerender方法;
调用handleStoreChange方法
调用handleStoreChange 更新方法,就是调用subscribe中的行参的方法,内部调用getSnapshot方法,将上次和这次的返回值对比,如果不相同就进行重新渲染;
update 阶段
调用HooksDispatcherOnUpdateInDEV.useSyncExternalStore 方法,调用check方法;
调用updateWorkInProgressHook方法,浅copy hook对象,调用两次getSnapshot方法,对比返回值,如果不同会报错,将getSnapshot返回结果更新到hook.memoizedState(上一次调用getSnapshot返回值);
js
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe])
updateEffect是useEffect更新阶段核心代码,在mountEffect更新部分中讲解;
同样更新pushEffect部分的hook
js
fiber.flags |= Passive;
pushEffect(
HookHasEffect | HookPassive,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
undefined,
null,
);
返回最新的nextSnapshot ;
同样在更新阶段执行useEffect时机时,参考useEffect中update阶段判断,也是在当前任务队列中执行;
根据updateEffect的核心代码,判断依赖项subscribe是否有改变,没有的话就不执行getSnapshot和返回函数,继续执行pushEffect创建的updateStoreInstance的函数,对比getSnapshot和返回值和上次的值,判断是否需要更新;
总结
目前还没找到很好的单独应用实例,据说,我听说最新版的redux就是使用useSyncExternalStore。