path:packages/react-reconciler/src/ReactFiberHooks.js
类型定义
Dispatcher 分发器
export type Dispatcher = {
readContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T,
useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: (I) => S,
): [S, Dispatch<A>],
useContext<T>(
context: ReactContext<T>,
observedBits: void | number | boolean,
): T,
useRef<T>(initialValue: T): {current: T},
useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void,
useLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void,
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T,
useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T,
useImperativeHandle<T>(
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void,
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
};
Dispatcher 类型定义了 10 个 hook 方法的基本形式,并额外增加一个读取上下文的方法 readContext。
Hooks 类型
export type HookType =
| 'useState'
| 'useReducer'
| 'useContext'
| 'useRef'
| 'useEffect'
| 'useLayoutEffect'
| 'useCallback'
| 'useMemo'
| 'useImperativeHandle'
| 'useDebugValue';
Update、UpdateQueue、Effect
type Update<S, A> = {
expirationTime: ExpirationTime,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
next: Update<S, A> | null,
};
// 更新队列
type UpdateQueue<S, A> = {
last: Update<S, A> | null,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
};
type Effect = {
tag: HookEffectTag,
create: () => (() => void) | void,
destroy: (() => void) | void,
deps: Array<mixed> | null,
next: Effect,
};
export type FunctionComponentUpdateQueue = {
lastEffect: Effect | null,
};
type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
Hook
export type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
renderWithHooks
在 React 深度学习:ReactHooks
一文中我已经看到,最重要的部分是 ReactCurrentDispatcher 对象,它有一个 current 属性,这个属性是 Dispatcher 类型的对象。但到目前我们还没有见到这个对象的初始化,或者叫对象的具体实现。
通读 ReactFiberHooks.js 的代码,发现有几个方法中有对 ReactCurrentDispatcher.current 赋值的代码:
renderWithHooksresetHooksdispatchAction
当然,为了严谨,我用 IDEA 查找了所有对 ReactCurrentDispatcher.current 的引用,未发现其他有使用到的地方。
renderWithHooks
export function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
refOrContext: any,
nextRenderExpirationTime: ExpirationTime,
): any {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
nextCurrentHook = current !== null ? current.memoizedState : null;
if (__DEV__) {
// do something
}
// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;
// remainingExpirationTime = NoWork;
// componentUpdateQueue = null;
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
// sideEffectTag = 0;
// TODO Warn if no hooks are used at all during mount, then some are used during update.
// Currently we will identify the update render as a mount because nextCurrentHook === null.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
// Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so nextCurrentHook would be null during updates and mounts.
if (__DEV__) {
// do something
} else {
ReactCurrentDispatcher.current =
nextCurrentHook === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
let children = Component(props, refOrContext);
if (didScheduleRenderPhaseUpdate) {
do {
didScheduleRenderPhaseUpdate = false;
numberOfReRenders += 1;
// 从列表的开头重新开始
nextCurrentHook = current !== null ? current.memoizedState : null;
nextWorkInProgressHook = firstWorkInProgressHook;
currentHook = null;
workInProgressHook = null;
componentUpdateQueue = null;
if (__DEV__) {
// do something
}
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnUpdateInDEV
: HooksDispatcherOnUpdate;
children = Component(props, refOrContext);
} while (didScheduleRenderPhaseUpdate);
renderPhaseUpdates = null;
numberOfReRenders = 0;
}
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
const renderedWork: Fiber = (currentlyRenderingFiber: any);
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
renderedWork.updateQueue = (componentUpdateQueue: any);
renderedWork.effectTag |= sideEffectTag;
if (__DEV__) {
// do something
}
// This check uses currentHook so that it works the same in DEV and prod bundles.
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null;
nextCurrentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
nextWorkInProgressHook = null;
if (__DEV__) {
// do something
}
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
sideEffectTag = 0;
// These were reset above
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
invariant(
!didRenderTooFewHooks,
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.',
);
return children;
}
上面的代码中有有两处赋值:
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnUpdateInDEV
: HooksDispatcherOnUpdate;
和
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
resetHooks
export function resetHooks(): void {
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// This is used to reset the state of this module when a component throws.
// It's also called inside mountIndeterminateComponent if we determine the
// component is a module-style component.
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null;
nextCurrentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
nextWorkInProgressHook = null;
if (__DEV__) {
// do something
}
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
sideEffectTag = 0;
didScheduleRenderPhaseUpdate = false;
renderPhaseUpdates = null;
numberOfReRenders = 0;
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
dispatchAction
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
if (__DEV__) {
// do something
}
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<S, A> = {
expirationTime: renderExpirationTime,
action,
eagerReducer: null,
eagerState: null,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
flushPassiveEffects();
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update: Update<S, A> = {
expirationTime,
action,
eagerReducer: null,
eagerState: null,
next: null,
};
// Append the update to the end of the list.
const last = queue.last;
if (last === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
const first = last.next;
if (first !== null) {
// Still circular.
update.next = first;
}
last.next = update;
}
queue.last = update;
if (
fiber.expirationTime === NoWork &&
(alternate === null || alternate.expirationTime === NoWork)
) {
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
if (__DEV__) {
prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
}
if (__DEV__) {
// do something
}
scheduleWork(fiber, expirationTime);
}
}
if (__DEV__) {
prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
和
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
但这都是开发环境的逻辑,不作过去阐述。
在上面三个方法中,我们看到 ReactCurrentDispatcher.current 分被赋予了如下值:
-
HooksDispatcherOnMount
-
HooksDispatcherOnUpdate
-
ContextOnlyDispatcher
-
HooksDispatcherOnUpdateInDEV
-
InvalidNestedHooksDispatcherOnUpdateInDEV
HooksDispatcherOnMount
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
};
该 hook dispatcher 在组件挂在时被使用,其中的每一个方法都有对应的实现。
HooksDispatcherOnUpdate
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
};
该 hook dispatcher 在组件更新时被使用,其中的每一个方法都有对应的实现。
ContextOnlyDispatcher
export const ContextOnlyDispatcher: Dispatcher = {
readContext,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
};
-
throwInvalidHookError 方法:
function throwInvalidHookError() { invariant( false, 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + '2. You might be breaking the Rules of Hooks\n' + '3. You might have more than one copy of React in the same app\n' + 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.', ); }该方法直接抛出了一个错误,错误信息说明这是一个无效的 hook 调用,原因:
- hook 只能在函数组件的主体内部调用
- 有 React 版本和呈现器(如React DOM) 不匹配
- 可能违反了 hook 的规则
- 可能在同一个应用程序中有多个 React 副本
通过以上信息我们不难看出,ContextOnlyDispatcher 不过错误调用信息的载体,它暴露同样的方法以能够在错误地调用的钩子时能正确回退。
其他限定于 DEV 的 HOOK
React 还有其他一些只用于开发环境的 hook dispatcher,他们不过是在 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate 中的方法上包装了一层,用以进行限定于开发环境的额外操作。
有兴趣的同学可以自己去翻翻源码。
Hook 方法
useState
-
mount
function mountState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { const hook = mountWorkInProgressHook(); if (typeof initialState === 'function') { initialState = initialState(); } hook.memoizedState = hook.baseState = initialState; const queue = (hook.queue = { last: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any), }); const dispatch: Dispatch< BasicStateAction<S>, > = (queue.dispatch = (dispatchAction.bind( null, // Flow doesn't know this is non-null, but we do. ((currentlyRenderingFiber: any): Fiber), queue, ): any)); return [hook.memoizedState, dispatch]; }从上面的代码我们可以看到:
-
useState的initialState参数可以是一个函数,这一点是官方文档没有讲到的 -
useState的返回值是一个数组,第一个元素为当前值;第二个置为一个将前两位参数限定为当前 Fiber 和当前更新队列queue的一个新dispatchAction方法,它用来改变这个值。
这个方法关键的地方在于,这个
dispatchAction是怎么样改变这个值的。dispatchAction经过如下处理后,只需要接收一个action参数:(dispatchAction.bind( null, // Flow doesn't know this is non-null, but we do. ((currentlyRenderingFiber: any): Fiber), queue, ): any) -
-
update
function updateState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { return updateReducer(basicStateReducer, (initialState: any)); }在组建更新时,调用
updateReducer方法以达到计算新的状态并触发更新的目的。
useReducer
-
mount
function mountReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>] { const hook = mountWorkInProgressHook(); let initialState; if (init !== undefined) { initialState = init(initialArg); } else { initialState = ((initialArg: any): S); } hook.memoizedState = hook.baseState = initialState; const queue = (hook.queue = { last: null, dispatch: null, lastRenderedReducer: reducer, lastRenderedState: (initialState: any), }); const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind( null, // Flow doesn't know this is non-null, but we do. ((currentlyRenderingFiber: any): Fiber), queue, ): any)); return [hook.memoizedState, dispatch]; }mountReducer接收三个参数:-
reducer:状态生成器函数reducer函数的形式为(state, action) => newState,state 为上一次存储的 state -
initialArg:初始值 -
init:可选参数,初始化函数,它将接收initialArg参数作为其唯一的参数执行,执行的结果作为初始状态。
-
-
update
function updateReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>] { const hook = updateWorkInProgressHook(); const queue = hook.queue; invariant( queue !== null, 'Should have a queue. This is likely a bug in React. Please file an issue.', ); queue.lastRenderedReducer = reducer; if (numberOfReRenders > 0) { // This is a re-render. Apply the new render phase updates to the previous // work-in-progress hook. const dispatch: Dispatch<A> = (queue.dispatch: any); if (renderPhaseUpdates !== null) { // Render phase updates are stored in a map of queue -> linked list const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); if (firstRenderPhaseUpdate !== undefined) { renderPhaseUpdates.delete(queue); let newState = hook.memoizedState; let update = firstRenderPhaseUpdate; do { // 处理此呈现阶段更新。 // 我们不需要检查优先级,因为它总是与当前渲染的相同 // 循环处理更新队列 const action = update.action; newState = reducer(newState, action); update = update.next; } while (update !== null); // 标记 fiber 执行的工作,但仅当新状态与当前状态不同时。 if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); } hook.memoizedState = newState; // 除非队列是空的,否则不要将渲染阶段更新累积的状态持久化到基本状态。 // TODO: Not sure if this is the desired semantics, but it's what we // do for gDSFP. I can't remember why. if (hook.baseUpdate === queue.last) { hook.baseState = newState; } queue.lastRenderedState = newState; return [newState, dispatch]; } } return [hook.memoizedState, dispatch]; } // 整个队列中的最后一次更新 const last = queue.last; // 最后一次更新,它是 base state 的一部分。 const baseUpdate = hook.baseUpdate; const baseState = hook.baseState; // 找到第一个未处理的更新。 let first; if (baseUpdate !== null) { if (last !== null) { // 对于第一次更新,队列是一个循环链表, // 其中`queue.last.next = queue.first`. // 一旦第一次更新提交,并且 `baseUpdate` 不再为空,我们就可以解开这个列表。 last.next = null; } first = baseUpdate.next; } else { first = last !== null ? last.next : null; } if (first !== null) { let newState = baseState; let newBaseState = null; let newBaseUpdate = null; let prevUpdate = baseUpdate; let update = first; let didSkip = false; do { const updateExpirationTime = update.expirationTime; if (updateExpirationTime < renderExpirationTime) { // Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state. if (!didSkip) { didSkip = true; newBaseUpdate = prevUpdate; newBaseState = newState; } // Update the remaining priority in the queue. if (updateExpirationTime > remainingExpirationTime) { remainingExpirationTime = updateExpirationTime; } } else { // Process this update. if (update.eagerReducer === reducer) { // If this update was processed eagerly, and its reducer matches the // current reducer, we can use the eagerly computed state. newState = ((update.eagerState: any): S); } else { const action = update.action; newState = reducer(newState, action); } } prevUpdate = update; update = update.next; } while (update !== null && update !== first); if (!didSkip) { newBaseUpdate = prevUpdate; newBaseState = newState; } // Mark that the fiber performed work, but only if the new state is // different from the current state. if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); } hook.memoizedState = newState; hook.baseUpdate = newBaseUpdate; hook.baseState = newBaseState; queue.lastRenderedState = newState; } const dispatch: Dispatch<A> = (queue.dispatch: any); return [hook.memoizedState, dispatch]; }updateReducer处理了当前组件的更新队列,它将遍历更新队列,为更新队列中的每一项调用 reducer,上一次计算出的状态会作为参数传递给下一次调用 reducer 的参数。最后将计算出的最终的新状态进行缓存,并将其进行返回。
useEffect
-
mount
function mountEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { return mountEffectImpl( UpdateEffect | PassiveEffect, UnmountPassive | MountPassive, create, deps, ); }mountEffect的具体实现:mountEffectImpl方法:function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; sideEffectTag |= fiberEffectTag; hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps); }使用
pushEffect方法返回一个Effect对象,并将其存储到 hook 的缓存中。pushEffect方法:创建一个
Effect对象,添加到componentUpdateQueue链表的末尾,并返回这个对象。function pushEffect(tag, create, destroy, deps) { const effect: Effect = { tag, create, destroy, deps, // 循环 next: (null: any), }; if (componentUpdateQueue === null) { componentUpdateQueue = createFunctionComponentUpdateQueue(); 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; } -
update
function updateEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { return updateEffectImpl( UpdateEffect | PassiveEffect, UnmountPassive | MountPassive, create, deps, ); }updateEffect的具体实现:updateEffectImpl方法:function updateEffectImpl(fiberEffectTag, hookEffectTag, 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; // 判断前后的 deps 是否相同 if (areHookInputsEqual(nextDeps, prevDeps)) { // 相同 // 创建一个新的 Effect,并把它添加到更新队列 // 但这里我们并不更新 hook 的缓存 pushEffect(NoHookEffect, create, destroy, nextDeps); return; } } } sideEffectTag |= fiberEffectTag; // 创建一个新的 Effect hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps); }
useLayoutEffect
-
mount
function mountLayoutEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { return mountEffectImpl( UpdateEffect, UnmountMutation | MountLayout, create, deps, ); }mountLayoutEffect的实现和updateEffect的实现一样。 -
update
function updateLayoutEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { return updateEffectImpl( UpdateEffect, UnmountMutation | MountLayout, create, deps, ); }updateLayoutEffect的实现和updateEffect的实现一样。
useImperativeHandle
-
mount
function mountImperativeHandle<T>( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, create: () => T, deps: Array<mixed> | void | null, ): void { if (__DEV__) { // do something } // TODO: If deps are provided, should we skip comparing the ref itself? const effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; return mountEffectImpl( UpdateEffect, UnmountMutation | MountLayout, imperativeHandleEffect.bind(null, create, ref), // 返回一个新 create effectDeps, ); } -
update
function updateImperativeHandle<T>( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, create: () => T, deps: Array<mixed> | void | null, ): void { if (__DEV__) { // do something } // TODO: If deps are provided, should we skip comparing the ref itself? const effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; return updateEffectImpl( UpdateEffect, UnmountMutation | MountLayout, imperativeHandleEffect.bind(null, create, ref), // 返回一个新的 create 函数 effectDeps, ); }
useImperativeHandle 与 useEffect 在具体实现上都依赖于 mountEffectImpl 和 updateEffectImpl 方法。
只不过 useImperativeHandle 需要多传一个参数——ref,这个参数可以是如下值:
{current: T | null}((inst: T | null) => mixed)null
当存在 deps 的情况下,ref 也会被添加到 deps 后面。
useImperativeHandle 的关键在于 imperativeHandleEffect 方法:
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;
if (__DEV__) {
// do something
}
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
此方法对原始传入的 create 和 ref 做了包装,返回一个新的函数。
useRef
-
mount
function mountRef<T>(initialValue: T): {current: T} { const hook = mountWorkInProgressHook(); const ref = {current: initialValue}; if (__DEV__) { Object.seal(ref); } hook.memoizedState = ref; return ref; }-
mountRef 在组件挂载时获取了一个新的 hook,并将传入的初始值存入了一个对象的
current属性中,这个对象被存入了 hook 的memoizedState属性中。并返回了一个 ref 对象。 -
为什么 Ref 是一个
{ current: any }的对象呢?仔细一想,如果我们存储的值是一个数字,那么我们想要改变这个值怎么办呢。好像真没办法。但如果是一个对象,那么我们返回的是对这个对象的引用,那么久可以使用ref.current对其进行赋值。function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已挂载到 DOM 上的文本输入元素 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }在这里,我们先声明了一个 ref,但是其赋值操作却是在 render 阶段完成的。
-
我们还可以看到,这里对初始值并没有什么要求,因此 Ref 可以任意的值。
注意:当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
-
-
update
function updateRef<T>(initialValue: T): {current: T} { const hook = updateWorkInProgressHook(); return hook.memoizedState; }这里就简洁明了了,获取了当前这个 hook,并直接读取返回了当前 hook 的缓存值。因此,使用
useRef返回的 ref 对象在组件的整个生命周期内保持不变。
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; }在组建挂载时,该方法创建了一个 hook,这个 hook 将存储传入的
callback和deps参数, 最后将callback进行了返回。 -
update
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; }在组建更新时,当前后的依赖相同时,始终返回之前 hook 中存储的回调函数;当依赖发生变化时,则返回一个新传入的回调函数。
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; }mountMemo两个参数,一个nextCreate函数和deps,它创建了一个 hook,并将nextCreate函数执行的返回值和deps作为数组存入 hook 的缓存中。最后返回
nextCreate函数执行的结果。 -
update
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) { // 假设这些都已定义。如果没有,areHookInputsEqual 会发出警告。 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; }与其他有传入
deps的 hook 一样,useMemo在组件更新时会检查前后两次传入的deps是否按序相等。 如果相等,则返回之前存储的nextCreate的执行结果;如果不等,则会重新计算该值,并将最新的结果和新的deps进行缓存。最后返回这个新的计算结果。
readContext、useContext
readContext 和 useContext 同时都是指向同一个方法,并且不区分 mount 和 update:
import {readContext} from './ReactFiberNewContext';
这个方法从其他模块导入,请参见 React 深入学习:ReactFiberNewContext。
useDebugValue
-
mount
function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void { // This hook is normally a no-op. // The react-debug-hooks package injects its own implementation // so that e.g. DevTools can display custom hook values. } -
update
const updateDebugValue = mountDebugValue;
其他方法
-
mountWorkInProgressHook
function mountWorkInProgressHook(): Hook { const hook: Hook = { memoizedState: null, baseState: null, queue: null, baseUpdate: null, next: null, }; if (workInProgressHook === null) { // 这是列表中的第一个钩子 firstWorkInProgressHook = workInProgressHook = hook; } else { // 添加到列表的末尾 workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; }workInProgressHook也是一个单项的链表结构,mountWorkInProgressHook该方法用于向workInProgressHook链表末尾添加 hook,并返回新增的这个 hook。 -
updateWorkInProgressHook
function updateWorkInProgressHook(): Hook { // This function is used both for updates and for re-renders triggered by a // render phase update. It assumes there is either a current hook we can // clone, or a work-in-progress hook from a previous render pass that we can // use as a base. When we reach the end of the base list, we must switch to // the dispatcher used for mounts. if (nextWorkInProgressHook !== null) { // There's already a work-in-progress. Reuse it. workInProgressHook = nextWorkInProgressHook; nextWorkInProgressHook = workInProgressHook.next; currentHook = nextCurrentHook; nextCurrentHook = currentHook !== null ? currentHook.next : null; } else { // Clone from the current hook. invariant( nextCurrentHook !== null, 'Rendered more hooks than during the previous render.', ); currentHook = nextCurrentHook; const newHook: Hook = { memoizedState: currentHook.memoizedState, baseState: currentHook.baseState, queue: currentHook.queue, baseUpdate: currentHook.baseUpdate, next: null, }; if (workInProgressHook === null) { // This is the first hook in the list. workInProgressHook = firstWorkInProgressHook = newHook; } else { // Append to the end of the list. workInProgressHook = workInProgressHook.next = newHook; } nextCurrentHook = currentHook.next; } return workInProgressHook; } -
createFunctionComponentUpdateQueue
创建函数组件的更新队列
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue { return { lastEffect: null, }; } -
areHookInputsEqual
遍历两个数组,判断他们的值是否按序相同,判断方法采用
Object.isfunction areHookInputsEqual( nextDeps: Array<mixed>, prevDeps: Array<mixed> | null, ) { if (prevDeps === null) { if (__DEV__) { // do something } return false; } if (__DEV__) { // do something } for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { if (is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true; } -
js
判断两个值是否相等,它是的 Object.is 函数的 Polyfill
path:packages/shared/objectIs.js
function is(x: any, y: any) { return ( (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare ); }
遗留问题
- userEffect 的 更新队列何时被调用