Hooks
- 在packages/react/src/ReactHooks.js目录下可以看见所有hooks,都是一个函数,内部调用了
resolveDispatcher
方法,然后使用对应函数名的方法
js
复制代码
export function useXXX(...args) {
const dispatcher = resolveDispatcher()
return dispatcher.useXXX(...)
}
function resolveDispatcher() {
const dispatcher = ReactSharedInternals.H
return dispatcher
}
- 在packages/react-reconciler/src/ReactFiberHook.js下有个renderWithHooks函数
js
复制代码
export function renderWithHooks(current: Fiber | null, workInProgress: Fiber) {
ReactSharedInternals.H =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
const HooksDispatcherOnMount = {
useCallback: mountCallback,
useContext: mountContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState
}
const HooksDispatcherOnUpdate = {
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
}
Hook
- hooks是以链表形式存储在fiber的memoizedState字段上,currentHook变量是当前fiber所属的列表,workInProgressHook变量是当前将要被添加进workInProgress fiber的列表
- 你可以理解成同时存在两棵 Fiber 树,一棵 Current 树,对应着目前已经渲染到页面上的内容;另一棵是 WorkInProgress 树,记录着即将发生的修改。将这两个 FiberNode 的 alternate 属性分别指向对方。
- 函数组件的 Hooks 也是在渲染阶段执行的。除了useContext ,Hooks 在挂载后,都会形成一个由 Hook.next 属性连接的单向链表,而这个链表会挂在 FiberNode.memoizedState 属性上。
js
复制代码
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
}
// 下面这部分充分说明了为什么hook必须在顶层,如果出现在if这种语句中,被添加的顺序可能不确定
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function updateWorkInProgressHook() {
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
if (nextCurrentHook === null) {
const currentFiber = currentlyRenderingFiber.alternate;
}
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list.
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
callback
- 可以将回调函数缓存起来,并且只在依赖项数组中的依赖项发生变化时才会重新创建新的函数实例,从而避免不必要的重新渲染。应用场景如下:渲染父子组件,父组件给子组件传递了一个函数,后续父组件更新,导致重新执行父函数,创建了新的函数实例传递给子组件,导致子组件的不必要的更新。
- 不建议对任何函数都使用该hook,毕竟进行缓存也需要一定性能。
js
复制代码
function mountCallback(cb, deps) {
const hook = mountWorkInProgressHook()
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [cb, nextDeps]
return cb
}
function updateCallback(cb, deps) {
const hook = updateWorkInProgressHook()
const nextDeps = deps === undefined ? null : deps
const prevState = hook.memoziedState
if (nextDeps !== null) {
const prevDeps = prevState[1]
if (areHookInputsEqual(nextDeps, prevDeps)) { // 对比依赖是否发生变化
return prevState[0];
}
}
hook.memoizedState = [cb, nextDeps];
return cb;
}
memo
- 与useCallback类似,也是用于缓存的,也可以避免子组件的冗余渲染,不过缓存的是计算结果。
js
复制代码
function mountMemo(nextCreate, deps) {
const hook = mountWorkInProgressHook()
const nextDeps = deps === undefined ? null : deps
const nextValue = nextCreate()
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateMemo(nextCreate, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// Assume these are defined. If they're not, areHookInputsEqual will warn.
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;
}
effect
- 对应到 Class 组件,那么 useEffect 就涵盖了 ComponentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。会在每次组件 render 完后判断依赖并执行。
- useEffect 这样会产生副作用的 Hooks,会额外创建与 Hook 对象一一对应的 Effect 对象,赋值给 Hook.memoizedState 属性。此外,也会在 FiberNode.updateQueue 属性上,维护一个由 Effect.next 属性连接的单向链表,并把这个 Effect 对象加入到链表末尾。
- 渲染完后异步执行,后续会按顺序先执行useEffect定义的的清除函数,然后执行其useEffect定义的副作用并保留清除函数供下次调用。
js
复制代码
function mountEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
createEffectInstance(),
nextDeps,
);
}
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
// currentHook is null on initial mount when rerendering after a render phase
// state update or for strict mode.
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
inst,
nextDeps,
);
}
function mountEffect(create, deps) {
mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps,
);
}
function updateEffect(create, deps) {
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
layoutEffect
- 和useEffect类似,不过是渲染之前同步执行。应用场景:需要在浏览器绘制前获取 DOM 元素的大小或位置,或者在浏览器绘制前修改 DOM。
- 源码和Effect一致,打上的flag不一致。
js
复制代码
function mountLayoutEffect(create, deps) {
return mountEffectImpl(UpdateEffect | LayoutStaticEffect;, HookLayout, create, deps);
}
functionn updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
ref
- useRef 看作是在函数组件之外创建的一个容器空间。在这个容器上,我们可以通过唯一的 current 属设置一个值,从而在函数组件的多次渲染之间共享这个值。
- 使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的
- 还可以保存某个 DOM 节点的引用。
js
复制代码
function mountRef(initialValue) {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
function updateRef(initialValue) {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
state
- 让函数组件具有维持状态的能力。在一个函数组件的多次渲染之间,这个 state 是共享的。
js
复制代码
function mountState(initialState) {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
const dispatch = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
));
queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}