hooks 实现
在beginWork阶段,为了获取函数式组件的children,在updateFunctionComponent函数中会调用renderWithHooks方法:
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);renderWithHooks方法在ReactFiberHooks.new.js文件中定义,它的关键代码为:
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
ReactCurrentDispatcher.current = ContextOnlyDispatcher;首先会根据current.memoizedState来判断当前是mount阶段还是update阶段,从而决定使用HooksDispatcherOnMount还是HooksDispatcherOnUpdate。然后执行Component,在执行的过程中,如果我们使用了hook,那么就会执行相应的hook方法。最后将ReactCurrentDispatcher.current置空。下面看一下hooks的实现。
useState
useState在React包中定义,其他的hooks也同样在这里定义:
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}第一步是resolveDispatcher获取当前的dispatcher。在renderWithHooks函数执行前已经通过判断mount/update将当前的dispatcher存放到全局,所以这里可以获取到。
mount 阶段
如果是mount阶段,useState对应的是mountState方法:
const HooksDispatcherOnMount: Dispatcher = {
useState: mountState,
}
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 创建一个 hook,挂载到 fiber上,并返回
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
// 初始化 queue
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}mountWorkInProgressHook方法
第一步执行的是mountWorkInProgressHook,该函数首先会创建一个hook,用于存放对应hook的状态:
const hook: Hook = {
// 记录该 hook 的 state
memoizedState: null,
// base state
baseState: null,
// base queue
baseQueue: null,
// 更新queue
queue: null,
// 指向下一个 hook
next: null,
};随后将hook形成链表结构,并将该链表存放到fiber.memoizedState当中:
if (workInProgressHook === null) {
// currentlyRenderingFiber 表示的是当前正在执行的的 函数 fiber。
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 遇到的 hooks 形成链表形式。
workInProgressHook = workInProgressHook.next = hook;
}这样后续就可以通过链表来访问每一个使用到的hook的状态了。
dispatchAction
在拿到hook后会初始化memoizedState,并将相应的memoizedState和dispatch函数返回。当调用dispatch函数时,实际触发的是dispatchAction函数,首先会创建一个update:
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
// update.eagerReducer = lastRenderedReducer;
eagerReducer: null,
// update.eagerState = lastRenderedReducer(currentState, action);
eagerState: null,
next: (null: any),
};如果当前正在处理这个fiber,触发了dispatch,那么会执行如下分支:
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 注意这个参数用于防止循环渲染
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
const pending = queue.pending;
// 将 update 形成链表形式
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}此时会将update形成循环链表,存放到queue.pending当中,并将didScheduleRenderPhaseUpdateDuringThisPass标记为true,这个标记在renderWithHooks中有用到:
if (didScheduleRenderPhaseUpdateDuringThisPass) {
let numberOfReRenders: number = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass = false;
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
numberOfReRenders += 1;
currentHook = null;
workInProgressHook = null;
workInProgress.updateQueue = null;
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnRerenderInDEV
: HooksDispatcherOnRerender;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
}如果为true的话,在函数组件执行完成后,会将dispatcher改为HooksDispatcherOnRerender并再次执行Component函数。并且这个重新渲染的次数不能大于最大次数RE_RENDER_LIMIT。
如果触发时的fiber与当前fiber不相等时,同样会建立循环链表的update存放到queue.pending当中,不同的是,此时会进行schedule:
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);rerender 阶段
在mount阶段我们提到了didScheduleRenderPhaseUpdateDuringThisPass字段为true时,如果当前函数执行完毕,此时会立即触发rerender,使用的是HooksDispatcherOnRerender,因此useState对应于rerenderState也是rerenderReducer函数。
updateWorkInProgressHook
updateWorkInProgressHook与mountWorkInProgressHook对应,它是用于取存放在fiber.memoizedState上的hook。
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
// 这个对应的 fiber 是 workInProgress
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}nextCurrentHook和currentHook对应于老fiber上的hooks,而nextWorkInProgressHook和workInProgressHook对应于新fiber上的hooks。当新fiber上的hook存在时,直接使用即可:
if (nextWorkInProgressHook !== null) {
// 如果 新fiber上存在该 hook, 可以直接使用
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
}否则会复用老fiber上的hook:
currentHook = nextCurrentHook;
// 如果新的 hook 为 null 时,需要将 current hook 作为 base,
// 进行创建新的 hook
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}更新state
在拿到对应的hook后,就会依次执行所有的update,更新当前的state:
// 循环执行
do {
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== firstRenderPhaseUpdate);
// 更新 state
hook.memoizedState = newState;
if (hook.baseQueue === null) {
hook.baseState = newState;
}
queue.lastRenderedState = newState;update 阶段
update阶段调用的是updateReducer,与rerender不同的是,循环执行update队列时,会判断updateLane是否在renderLanes中:
if (!isSubsetOfLanes(renderLanes, updateLane)) {}这个阶段的更新与UpdateQueue章节中class组件的processUpdate方法更新类似,这里不再赘述。
useReducer
useReducer的流程就与useState一致,不同的是传入的reducer是用户定义的reducer,并且dispatch的时候会携带action。在useState中,reducer是state => state,dispatch的时候无需action。
useEffect
mount阶段
mount阶段useEffect调用的是mountEffect:
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
)
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
}mountWorkInProgressHook在useState中已经讲过,其主要作用是创建一个hook,并将该hook与其他hook形成链表存放到fiber.memoizedState属性上。
currentlyRenderingFiber.flags |= fiberFlags这里表示如果使用了useEffect这个hook,那么fiber.flags上会增加Passive和PassiveStatic这两个标识符。
接下来是pushEffect方法,首先会创建一个effect:
const effect: Effect = {
// 表示 hook 的 tag
tag,
// 创建时的回调函数
create,
// 销毁时的回调函数
destroy,
// hook 的依赖
deps,
// 指向下一个 effect
next: (null: any),
};该effect的tag为HasEffect | Passive,随后将这个effect与其他的effect形成链表结构存放到fiber.updateQueue中:
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
// 如果不存在 updateQueue,进行初始化
if (componentUpdateQueue === null) {
// 建立循环链表
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 如果存在updateQueue,将effect添加到链表中
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 添加到链表的前面
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}这里fiber.updateQueue.lastEffect指向的是新添加的effect。
rerender和update阶段
rerender和update阶段都是调用的updateEffect方法,也是调用的updateEffectImpl方法。首先会获取相应的hook:
const hook = updateWorkInProgressHook()随后会判断依赖deps有没有发生变化。如果没有发生变化,那么pushEffect的时候,effect.tags不包含HasEffect。反之,则会包含。
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);useLayoutEffect
mount 阶段
useLayoutEffect的mount阶段与useEffect几乎一致,主要的区别有两点:
fiber的flags为Update | LayoutStatic。effect的tag为HasEffect | Layout。
rerender和update阶段
与useEffect一致,主要的区别在于:
fiber的flags为UpdateEffect。effect的tag为HasEffect | Layout。
useMemo
mount 阶段
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}首先获取hook,然后执行nextCreate并将结果返回。
rerender和update阶段
updateMemo方法主要用于判断前后的deps是否一致。如果一致,则跳过函数执行,将先前的值返回,从而达到缓存的目的。
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}useCallback
mount 阶段
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}memoizedState存储的是callback和deps。
rerender和update阶段
对比deps看是否发生变化,如果没变,则将原callback返回。
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}useContext
mount、rerender和update阶段
useContext三个阶段都是执行的readContext方法,首先会获取到context的value值,也就是用户定义的context的值。
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;随后判断lastFullyObservedContext === context,如果为true,说明该context已经被处理了,无需重复处理,跳过即可。
if (lastFullyObservedContext === context) {
// Nothing to do. We already observe everything in this context.
}否则的话,会创建一个contextItem,形成链表并存放到fiber.dependencies.firstContext上:
const contextItem = {
context: ((context: any): ReactContext<mixed>),
memoizedValue: value,
next: null,
};
if (lastContextDependency === null) {
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
};
} else {
lastContextDependency = lastContextDependency.next = contextItem;
}这里的处理与class组件的context处理类似。
useRef
mount 阶段
function mountRef<T>(initialValue: T): {| current: T |} {
const hook = mountWorkInProgressHook();
const ref = { current: initialValue };
hook.memoizedState = ref;
return ref;
}mountRef方法创建了一个{ current: xxx }对象并将其返回。
rerender和update阶段
function updateRef<T>(initialValue: T): {| current: T |} {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}updateRef也非常的简单,将hook.memoizedState中存储的{ current: xxx }对象返回。
useTransition
mount 阶段
useTransition的实现是基于useState的:
function mountTransition(): [boolean, (() => void) => void] {
const [isPending, setPending] = mountState(false);
const start = startTransition.bind(null, setPending);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [isPending, start];
}首先会通过useState定义isPending和setPending方法,然后定义一个start方法并将其返回:
function startTransition(setPending, callback) {
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(
higherEventPriority(previousPriority, ContinuousEventPriority),
);
// 添加一个 update,优先级为当前 update 优先级
setPending(true);
// 此时进行 schedule 的时候,requestUpdateLane 为原始的 lane
// 比如在 click 的时候就会是同步 lane
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
try {
// 添加一个 update,requestUpdateLane的结果为 transition lane
setPending(false);
// 执行 startTransition 的回调
callback();
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}首先会设置更新时的优先级,这样在setPending的时候,会进行requestUpdateLane,拿到对应的lane。随后将ReactCurrentBatchConfig.transition置为1,在requestUpdateLane会返回transition lane。因此try内部setPending(false)是一个transition lane的更新。同样callback内部定义的更新也将会是一个transition lane的更新。这样在transition更新的时候,setPending(false)会执行。
rerender和update阶段
function updateTransition(): [boolean, (() => void) => void] {
const [isPending] = updateState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
return [isPending, start];
}update阶段比较简单,拿到isPending和start方法返回即可。
useDeferredValue
mount、rerender和update阶段
useDeferredValue的实现是基于useState和useEffect的:
function mountDeferredValue<T>(value: T): T {
const [prevValue, setValue] = mountState(value);
mountEffect(() => {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
try {
setValue(value);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
}, [value]);
return prevValue;
}在useEffect内部设置value时,会将ReactCurrentBatchConfig.transition置为1,因此更新对应的lane是transition lane。
