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
。