目录
前言
本文是React源码学习系列第七篇,该系列整体都基于React18.0.0版本源码。旨在学习过程中记录一些个人的理解。该篇介绍React Hooks。
FC组件入口
function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// ...
// 根据执行环境给ReactCurrentDispatcher.current赋值。
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
// render完以后更改ReactCurrentDispatcher.current指向
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// ...
}
dispatcher
React Hooks中,组件“mount时的hook”与“update时的hook”来源于不同的对象,这类对象被称为dispatcher。
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
unstable_isNewReconciler: enableNewReconciler,
};
//更新时候的hooks
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
};
在进入FC的render流程前,会根据“FC对应的fiberNode的如下判断条件”为ReactCurrentDispatcher.current赋值。其中current.memoizedState保存FC组件的第一个hoook,在FC组件render时,可以从ReactCurrentDispatcher.current中获取“当前上下文环境Hooks对应的实现”。
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
这样设计的目的是检测“Hooks执行的上下文环境”。考虑如下情况,当错误地书写了嵌套形式的hook。
useEffect(() => {
useState(0);
})
此时ReactCurrentDispatcher.current已经指向ContextOnlyDispatcher,所以执行useState方法时,实际执行的是throwInvalidHookError方法。
const ContextOnlyDispatcher: Dispatcher = {
readContext,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useInsertionEffect: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
};
Hooks数据结构
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: queue,
// 指向下一个hook
next: null,
};
fiberNode也有memoizedState字段,它存的是FC组件对应fiberNode的第一个hook。
hook.memoizedState根据不同hook保存的对应的数据。
- useState -> state
- useReducer -> state
- useEffect -> callback, [...deps]
- useRef -> {current: initialValue}
- useMemo -> [callback(), [...deps]]
- useCallback -> [callback, [...deps]]
- baseState是参与计算的初始state,如果没有遗留的Upate,那么等于memoizedState,有遗留的Upate会保存中间状态。
- baseQueue是本次更新前遗留的Upate,部分Update因为优先级低,在上次render阶段没有参与state的计算。会以链表形式保存。
- queue是触发更新后,产生的Update会保存在queue.pending。queue.pending指向最后一个Update。queue.pending.next指向第一个Update。
- 开始计算state前会把两个链表连接成新的链表。
Hooks执行流程
- FC进入render流程前,确定ReactCurrentDispatcher.current指向。
- 进入mount流程时,执行mount对应逻辑,方法名一般为“mountXXX”(其中XXX替换为hook名称,如mountState)。
- 进入update流程时,执行update对应逻辑,方法名一般为“updateXXX”(其中XXX替换为hook名称,如updateState)。
- 其他情况hook执行,依据ReactCurrentDispatcher.current指向做不同处理。
mount流程
function mountXXX() {
// 获取对应hook
const hook = mountWorkInProgressHook();
// ...
}
mountWorkInProgressHook
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// currentlyRenderingFiber为当前FC对应fiberNode
// 第一个hook保存在fiberNode.memoizedState属性上
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 加入已有hooks链表
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
update流程
function updateXXX() {
// 获取对应hook
const hook = updateWorkInProgressHook();
// ...
}
updateWorkInProgressHook
function updateWorkInProgressHook(): Hook {
// renderWithHooks方法会设为null,说明是FC组件的第一个hook
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
// update
if (current !== null) {
// current FiberNode的第一个hook
nextCurrentHook = current.memoizedState;
} else {
// ???暂时不知道什么情况会走这里
nextCurrentHook = null;
}
} else {
// 不是第一个hook,直接取链表的下一个hook
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
// renderWithHooks方法会设为null,说明是FC组件的第一个hook
if (workInProgressHook === null) {
// FiberNode的第一个hook,默认为null
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
// 不是第一个hook,直接取链表的下一个hook
nextWorkInProgressHook = workInProgressHook.next;
}
// 不为null,说明是render阶段发生的更新
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else { // 正常情况下
// hook对不上,说明有在判断里面写hook
if (nextCurrentHook === null) {
// 渲染了比之前更多的钩子。
throw new Error('Rendered more hooks than during the previous render.');
}
currentHook = nextCurrentHook;
// 从currentHook clone属性
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;
}
}
return workInProgressHook;
}
- 正常update流程,会克隆currentHook作为workInProgressHook并返回。
- render阶段触发的更新,因为上一轮render阶段已经创建了workInProgressHook,直接返回它。
useState
function mountState<S>(
initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
// 创建hook
const hook = mountWorkInProgressHook();
// 函数类型value
if (typeof initialState === 'function') {
initialState = initialState();
}
// 初始化memoizedState
hook.memoizedState = hook.baseState = initialState;
// 初始化hook.queue
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 创建hook
const hook = mountWorkInProgressHook();
// 初始化memoizedState
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
// 初始化hook.queue
const queue: UpdateQueue<S, A> = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
对比发现,mount时两个Hook的主要区别主要体现在queue.lastRenderedReducer属性上,其代表“上一次render时使用的reducer”。
- useState的lastRenderedReducer为basicStateReducer。
- useReducer的lastRenderedReducer为“传入的reducer”参数。
所以,useState可以视为“reducer参数为basicStateReducer的useReducer”。
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
update时useState与useReducer执行同一个函数updateReducer。
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current: Hook = (currentHook: any);
// 上次计算的state,如果没有update因为优先级不够未参与计算,baseQueue与memoizedState相同。
let baseQueue = current.baseQueue;
// 本次更新产生的update链表
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// 有update因为优先级不够未参与计算。
if (baseQueue !== null) {
// 把baseQueue和pendingQueue链接起来
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// 拼接完后的baseQueue,按顺序执行
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateLane = update.lane;
// 优先级不足
if (!isSubsetOfLanes(renderLanes, updateLane)) {
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
// 优先级不足的update 都挂到newBaseQueue下面
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
} else { // 优先级够
// 前面有update优先级不够,后面的update就算优先级足够也不能调度
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
lane: NoLane, // 设为同步优先级,下一次调度一定可以执行
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 如果有急迫的状态直接用,setState的时候调度前提前计算好的。参考前一篇文章性能优化eagerState策略。
if (update.hasEagerState) {
newState = ((update.eagerState: any): S);
} else {
// 没有就计算state
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
// 没有因为优先级不足而没参与计算的update
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
// 有优先级不足没参与计算的update,首尾相连
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// state不同,说明有更新
if (!is(newState, hook.memoizedState)) {
// 把didReceiveUpdate设为true,后面就不能命中bailout
markWorkInProgressReceivedUpdate();
}
// 如果没有因为优先级不足而没参与计算的update,newState与newBaseState相同
hook.memoizedState = newState;
// 如果有因为优先级不足而没参与计算的update,newBaseState作为下次调度时候计算的基state
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
// 忽略interleaved相关
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
- 拼接baseQueue和pending.Queue。
- 遍历baseQueue上的update计算state,遇到优先级不够的,把该update及之后的update都保存在baseQueue上,把中间state保存在baseState。如果所有update都参与了计算,baseState与memoizedState相同。
queue拼接图
dispatch
useState与useReducer的dispatch方法逻辑基本相同,useState 的做了eagerState优化策略判断。关于优化策略可查看前一篇性能优化篇。没有命中eagerState优化策略会走发起调度逻辑。
function dispatchSetState<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
hasEagerState: false,
eagerState: null,
next: (null: any),
};
if (isRenderPhaseUpdate(fiber)) { // render阶段的更新
enqueueRenderPhaseUpdate(queue, update);
} else {
enqueueUpdate(fiber, queue, update, lane);
const alternate = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 判断wip、current的lanes是否为NoLanes
const lastRenderedReducer = queue.lastRenderedReducer;
// 上次计算时使用的reducer
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any);
// 直接计算出state
const eagerState = lastRenderedReducer(currentState, action);
// 将计算出来的状态存储在update上。
update.hasEagerState = true;
update.eagerState = eagerState;
// Object.is
if (is(eagerState, currentState)) {
// 状态与当前状态相同,直接return
return;
}
}
}
}
// 没有命中优化策略,发起调度
const eventTime = requestEventTime();
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
Effect相关hook
React共有三个Effect相关的hook。
- useEffect:commit阶段完成后异步执行,不会阻塞视图渲染。
- useLayoutEffect:commit阶段的Layout子阶段同步执行,一般用于“DOM相关操作”。
- useinsertionEffect:commit阶段的Mutation子阶段同步执行,无法访问“DOM的引用”,一般用于CSS-in-JS。
数据结构
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
- tag有三个类型
- const Insertion = 0b0010; -> useInsertionEffect
- const Layout = 0b0100; -> useLayoutEffect
- const Passive = 0b1000; -> useEffect
- create 回调函数
- destroy 组件销毁时执行函数
- deps 依赖项
- next字段指向当前Fiber的其他Effect,形成环状列表。
// 这里是tag
useEffect(() => {
// 这里是create
return () => {
// 这里是destroy
}
},[]) // 这里是deps
整体工作流程分为三个阶段。
申明阶段
mount 与 update分别对应mountEffectImpl 与 updateEffectImpl方法,区别在于update时会比较deps是否变化。
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
updateEffectImpl
判断deps是否有变化,有无变化决定是否打上HookHasEffect标记。注意:就算dpes没有变化还是会创建Effect,保持Effect链表中的effect数量、顺序稳定。
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
// 依赖项是否相同-浅比较
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
// 依赖项有更新 打上对应的fiberFlags Passive为 0b00000000000000100000000000
// flushPassiveEffects里面会判断该flags
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags, // HookHasEffect 表示该hook需要触发Effect
create,
destroy,
nextDeps,
);
}
pushEffect
组成链表,保存在fiberNode.updateQueue.lastEffect上。commit阶段的三个字阶段会去执行相对应的Effect。
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
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;
}
}
return effect;
}
调度阶段(useEffect独有)
// 检查整棵Fiber树上是否存在useEffect,有的话安排一个调度异步执行。
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
// 已默认优先级调度执行useEffect。
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
由于调度阶段的存在,为了保证下一次commit阶段执行前“本次commit阶段调度的useEffect”都已执行,commit阶段会在入口处执行flushPassiveEffects方法,以保证本次commit阶段执行时,不存在“还在调度中,为执行的useEffect”。
do {
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
flushPassiveEffects方法内会flushSyncCallbacks方法,遍历执行所有同步优先级调度的更新,执行过程中有可能会产生新的useEffect,所以需要放在do while循环中,确保所有useEffect回调都执行完毕。
执行阶段
根据不同的Effect Tag执行对应的Effect回调方法。 具体参考React Commit阶段篇-commitRoot。
useMemo
useMemo用于缓存一个值。
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;
}
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// 存在依赖项
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 浅比较依赖项
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
const nextValue = nextCreate();
// 更新缓存的数据
hook.memoizedState = [nextValue, nextDeps];
// 返回新的数据
return nextValue;
}
useCallback
useCallback用于缓存一个函数。
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;
}
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// 存在依赖项
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 浅比较依赖项
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
// 更新缓存的数据
hook.memoizedState = [callback, nextDeps];
// 返回新的数据
return callback;
}
mount时useMemo和useCallback唯一的区别是useMemo会执行传入的方法,缓存得到的返回值。useCallback直接缓存传入的方法。 update时大致跟mount差不多,增加了deps是否变化的判断逻辑,如果没有变化,直接返回缓存的值,有变化则更新缓存值,并返回新的值。
useRef
ref用于保存任何”需要被引用的数据,包括不限于DOM元素“。
- 函数类型
- {current: T}
- String类型,已不推荐使用。
function mountRef<T>(initialValue: T): {|current: T|} {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
function updateRef<T>(initialValue: T): {|current: T|} {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
React.createRef也是一样的。
function createRef(): RefObject {
const refObject = {
current: null,
};
return refObject;
}
本质就是缓存了一个对象引用。
ref工作流程
- render阶段beginWork方法内会打上Ref标记。
- commit阶段Mutaition子阶段会对有Ref标记的fiberNode重置为null。
- commit阶段Layout子阶段会对有Ref标记的fiberNode重新赋上新值
// 保留核心代码
function markRef(current: Fiber | null, workInProgress: Fiber) {
const ref = workInProgress.ref;
if (
(current === null && ref !== null) ||
(current !== null && current.ref !== ref)
) {
// 打上ref标记
workInProgress.flags |= Ref;
}
}
// 保留关键代码
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
if (typeof currentRef === 'function') {
currentRef(null);
} else {
currentRef.current = null;
}
}
}
// 保留关键代码
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
let instanceToUse;
switch (finishedWork.tag) {
case HostComponent:
// 浏览器端是返回自己,估计是为了兼容多端
instanceToUse = getPublicInstance(instance);
break;
default:
instanceToUse = instance;
}
if (typeof ref === 'function') {
// 函数方式设置的ref ref={(ref)=> this.ref = ref}
let retVal = ref(instanceToUse);
} else {
// ref = {ref}
ref.current = instanceToUse;
}
}
}
useImperativeHandle
可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,。 useImperativeHandle 应当与 forwardRef 一起使用。
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => { inputRef.current.focus();
}}));
return <input ref= {inputRef} ... />;
}
// 转发ref
FancyInput = forwardRef(FancyInput);
<FancyInput ref={inputRef} / >
function mountImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
let fiberFlags: Flags = UpdateEffect;
return mountEffectImpl(
fiberFlags,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
function updateImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
return updateEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
跟Effect一样调用updateEffectImpl,区别是Effect Hook第三个参数是传入的create方法。useImperativeHandle是自己实现了一个Effect包装了传入的create方法。
function imperativeHandleEffect<T>(
create: () => T,
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
) {
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => {
refCallback(null);
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
useTransition
useTransition用于“以较低优先级调度一个更新”。 useTransition的实现很简单,由useState 与 startTransition方法组合构成,内部维护了一个状态isPending。
例子
function App() {
let [num, setNum] = useState(0);
const [isPending, startTransition] = useTransition();
return (
<div style={{color: isPending ? 'red': 'black'}} onClick={() => {
update(22222);
startTransition(() => update(44444))
}}>
{num}
</div>
)
}
快速多次点击div后,视图中会闪现红色的“22222”,并最终显示黑色的“44444”。
实现
function mountTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [isPending, setPending] = mountState(false);
const start = startTransition.bind(null, setPending);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [isPending, start];
}
function updateTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
const [isPending] = updateState(false);
const hook = updateWorkInProgressHook();
const start = hook.memoizedState;
return [isPending, start];
}
function startTransition(setPending, callback, options) {
// 缓存之前更新优先级
const previousPriority = getCurrentUpdatePriority();
// 设置更新优先级
setCurrentUpdatePriority(
higherEventPriority(previousPriority, ContinuousEventPriority),
);
// 触发isPending状态更新
setPending(true);
// 缓存之前transition上下文
const prevTransition = ReactCurrentBatchConfig.transition;
// 设置当前transition上下文
ReactCurrentBatchConfig.transition = {};
const currentTransition = ReactCurrentBatchConfig.transition;
try {
// 触发isPending状态更新
setPending(false);
// 执行回调函数
callback();
} finally {
// 恢复之前优先级及transition上下文
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
调度前生成update时会去生成一个lane,里面判断了是否在transition上下文。
const NoTransition = null;
// 预留16个TransitionLane
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /* */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /* */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /* */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /* */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /* */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000001000000000000000000000;
function requestUpdateLane(fiber: Fiber): Lane {
// 是否transition上下文
const isTransition = requestCurrentTransition() !== NoTransition;
// 是transition上下文
if (isTransition) {
if (currentEventTransitionLane === NoLane) {
// 拿到下一个空闲的TransitionLane
currentEventTransitionLane = claimNextTransitionLane();
}
return currentEventTransitionLane;
}
}
function claimNextTransitionLane(): Lane {
// 循环分配过渡车道
const lane = nextTransitionLane;
nextTransitionLane <<= 1;
// 如果是lane超过过渡TransitionLanes最高位了
if ((nextTransitionLane & TransitionLanes) === 0) {
// 赋值为第一位
nextTransitionLane = TransitionLane1;
}
return lane;
}
本质是代码内部手动降低了优先级。
useDeferredValue
useTransition为开发者提供了“操作优先级”的功能。开发者可以自行决定“哪些状态更新是‘低优先级更新’”。但有时开发者可能无法访问“触发状态更新的方法”(比如使用第三方库时),此时可以用useDeferredValue。 useDeferredValue接受一个状态并返回“该状态的拷贝”。当原始状态变化后,拷贝对象会以较低优先级更新。useDeferredValue的实现与useTransition类似,都是基于transition上下文。
function mountDeferredValue<T>(value: T): T {
const [prevValue, setValue] = mountState(value);
mountEffect(() => {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = {};
try {
setValue(value);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
}, [value]);
return prevValue;
}
function updateDeferredValue<T>(value: T): T {
const [prevValue, setValue] = updateState(value);
updateEffect(() => {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = {};
try {
setValue(value);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
}, [value]);
return prevValue;
}
总结
该片介绍了React常用的几个Hook的用法及实现原理。
参考
React设计原理 - 卡颂