React Hooks原理解析
这里先简单列举我们常用的几种hooks,不熟悉的同学建议去官网看下,自己实践下。
React 常用的 hooks 包括:
useState
:用于在函数组件中添加状态。useEffect
:允许在每次渲染后执行副作用操作,如订阅和取消订阅事件、更新DOM等。useContext
:用于在组件之间共享数据,而不必通过 props 层层传递。useReducer
:将多个状态相关的操作归纳到一个 reducer 函数中,类似于 Redux 的reducer。useCallback
:返回一个记忆化的函数引用,以避免在每次渲染时重新创建该函数。useMemo
:允许缓存计算结果,只有在依赖项发生变化时才会重新计算。useRef
:返回一个可变的 ref 对象,可以用来保存任何可变值,而且不会触发组件重新渲染。useLayoutEffect
:与useEffect
类似,但是它会在页面布局完成后同步执行,可以获取最新的 DOM 布局信息。useImperativeHandle
: 在使用 ref 时,允许自定义暴露给父组件的实例值。useDebugValue
:可以为自定义 hook 提供调试信息,在 React 开发者工具中显示。
Hooks原理实现
流程图如下:renderWithHooks
根据current来判断当前是首次渲染还是更新。
hooks加载时调用对应的mount函数,更新时调用对应的update函数。
hooks生成单向链表,通过next连接,最后一个next指向null。
state hooks会生成update循环链表, effects会生成另外一个effectList循环链表。
renderWithHooks
react-reconciler/src/ReactFiberHooks.js
### renderWithHooks中判断是否是首次渲染
function renderWithHooks(current, workInProgress, Component, props, nextRenderLanes) {
//当前正在渲染的车道
renderLanes = nextRenderLanes
currentlyRenderingFiber = workInProgress;
//函数组件更新队列里存的effect
workInProgress.updateQueue = null;
//函数组件状态存的hooks的链表
workInProgress.memoizedState = null;
//如果有老的fiber,并且有老的hook链表
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
}
//需要要函数组件执行前给ReactCurrentDispatcher.current赋值
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
renderLanes = NoLanes;
return children;
}
HooksDispatcherOnMount
和HooksDispatcherOnUpdate
对象分别存放hooks的挂载函数和更新函数
// hooks加载的对象,包含useReducer useState useEffect useLayoutEffect useRef等,还有18版本新增的useTransition,useId等
const HooksDispatcherOnMount = {
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,
};
// hooks更新的对象
const HooksDispatcherOnUpdate = {
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,
};
hooks的注册
react\src\ReactHooks.js
function resolveDispatcher() {
return ReactCurrentDispatcher.current;
}
/**
*
* @param {*} reducer 处理函数,用于根据老状态和动作计算新状态
* @param {*} initialArg 初始状态
*/
export function useReducer(reducer, initialArg) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg);
}
export function useState(initialState) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
export function useEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
export function useLayoutEffect(create, deps) {
const dispatcher = resolveDispatcher();
return dispatcher.useLayoutEffect(create, deps);
}
export function useRef(initialValue) {
const dispatcher = resolveDispatcher();
return dispatcher.useRef(initialValue);
}
/**
* 构建新的hooks, 其主要作用是在 Fiber 树中遍历到某个组件时,
* 根据该组件的类型和当前处理阶段(mount 或 update),处理该组件的 Hook 状态。
*/
function updateWorkInProgressHook() {
//获取将要构建的新的hook的老hook
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
currentHook = current.memoizedState;
} else {
currentHook = currentHook.next;
}
//根据老hook创建新hook
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue
}
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
return workInProgressHook;
}
useRef
比较简单的hook,useRef
是一个 Hook 函数,它返回一个可变的 ref 对象。useRef
的常见用法是保存组件中某个 DOM 元素的引用。
使用 useRef
需要先调用该 Hook,并将其返回值赋给一个变量(通常为 ref
)。然后可以将这个变量作为 ref
属性传递给需要引用的 DOM 元素
// <!-- mountRef -->
function mountRef(initialValue) {
const hook = mountWorkInProgressHook();
const ref = {
current: initialValue
}
hook.memoizedState = ref;
return ref;
}
function updateRef() {
const hook = updateWorkInProgressHook();
return hook.memoizedState;
}
useState
接收一个初始状态值,返回一个数组,包含当前状态值和更新状态值的方法。可以通过调用更新方法来改变状态值,并触发组件的重新渲染
//useState其实就是一个内置了reducer的useReducer
/**
* hook的属性
* hook.memoizedState 当前 hook真正显示出来的状态
* hook.baseState 第一个跳过的更新之前的老状态
* hook.queue.lastRenderedState 上一个计算的状态
*/
function mountState(initialState) {
const hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState = initialState;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: baseStateReducer,//上一个reducer
lastRenderedState: initialState//上一个state
}
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function dispatchSetState(fiber, queue, action) {
// 获取当前的更新赛道 1
const lane = requestUpdateLane();
const update = {
lane,//本次更新优先级就是1
action,
hasEagerState: false,//是否有急切的更新
eagerState: null,//急切的更新状态
next: null
}
const alternate = fiber.alternate;
//当你派发动作后,我立刻用上一次的状态和上一次的reducer计算新状态
//只要第一个更新都能进行此项优化
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes == NoLanes)) {
//先获取队列上的老的状态和老的reducer
const { lastRenderedReducer, lastRenderedState } = queue;
//使用上次的状态和上次的reducer结合本次action进行计算新状态
const eagerState = lastRenderedReducer(lastRenderedState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (Object.is(eagerState, lastRenderedState)) {
return;
}
}
//下面是真正的入队更新,并调度更新逻辑
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
//useState其实就是一个内置了reducer的useReducer
function baseStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
function updateState(initialState) {
return updateReducer(baseStateReducer, initialState);
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
//把新旧更新链表合并
if (pendingQueue !== null) {
if (baseQueue !== null) {
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
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;
const shouldSkipUpdate = !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
const clone = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null,
};
// 省略部分代码
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
if (baseQueue === null) {
queue.lanes = NoLanes;
}
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
useReducer
使用useReducer需要传入两个参数:reducer函数和初始状态(initialState)。reducer函数接收当前状态和action对象作为参数,并返回一个新的状态值。dispatch函数用于触发状态更新,它会向reducer函数传递一个action对象。
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialArg
}
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
/**
* 执行派发动作的方法,它要更新状态,并且让界面重新更新
* @param {*} fiber function对应的fiber
* @param {*} queue hook对应的更新队列
* @param {*} action 派发的动作
*/
function dispatchReducerAction(fiber, queue, action) {
//在每个hook里会存放一个更新队列,更新队列是一个更新对象的循环链表update1.next=update2.next=update1
const update = {
action,//{ type: 'add', payload: 1 } 派发的动作
next: null//指向下一个更新对象
}
//把当前的最新的更添的添加更新队列中,并且返回当前的根fiber
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
useEffect
用于在组件渲染后执行副作用操作,比如获取数据、订阅事件等操作。它接收两个参数:第一个参数是一个回调函数,用于执行副作用操作;第二个参数是一个数组,用于指定副作用操作依赖的状态变量,只有这些状态变量发生改变时,useEffect回调才会重新执行。
在 React 的源码中,每个 useEffect 都会创建一个 Effect 对象,并将其存储在组件实例上的 effect list 中。当组件被卸载时,React 会遍历该列表并按照相反的顺序调用每个 Effect 对象的清除函数。
function createEffect(
tag: SideEffectTag,
create: () => mixed,
destroy: (() => mixed) | null,
deps: DependencyList | void | null
): Effect {
return {
tag,
create,
destroy,
deps,
// Circular linked list of effects attached to a component
next: (null: any),
};
}
其中,create 表示 Effect 对象的创建函数,destroy 表示清除函数。在组件挂载时,React 会调用 create 函数来执行副作用操作,并将其返回值存储在 Effect 对象的 create 属性上。在组件卸载时,React 会判断 destroy 属性是否存在,如果存在,则调用 destroy 函数来清除副作用操作。最后,React 会将该 Effect 对象从 effect list 中移除,确保正确地释放资源和取消订阅。
function mountEffect(create, deps) {
return mountEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
//给当前的函数组件fiber添加flags
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps
);
}
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
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;
}
function updateEffect(create, deps) {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy;
//上一个老hook
if (currentHook !== null) {
//获取此useEffect这个Hook上老的effect对象 create deps destroy
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
// 用新数组和老数组进行对比,如果一样的话
if (areHookInputsEqual(nextDeps, prevDeps)) {
//不管要不要重新执行,都需要把新的effect组成完整的循环链表放到fiber.updateQueue中
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
//如果要执行的话需要修改fiber的flags
currentlyRenderingFiber.flags |= fiberFlags;
//如果要执行的话 添加HookHasEffect flag
//刚才有同学问 Passive还需HookHasEffect,因为不是每个Passive都会执行的
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
destroy,
nextDeps
);
}
// useLayoutEffect和useEffect都调用了updateEffectImpl, flagFiber不同
//export const Update = /* */ 0b000000000000000000000000100;
// /export const Passive = /* */ 0b000000000000000100000000000;
function updateLayoutEffect(create, deps) {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}
function performConcurrentWorkOnRoot(root: FiberRoot, didTimeout: boolean) {
const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
// Something in the passive effect phase may have canceled the current task.
// Check if the task node for this root was changed.
if (root.callbackNode !== originalCallbackNode) {
// The current task was canceled. Exit. We don't need to call
// `ensureRootIsScheduled` because the check above implies either that
// there's a new task, or that there's no remaining work on this root.
return null;
} else {
// Current task was not canceled. Continue.
}
}
}
export function flushPassiveEffects(): boolean {
ReactCurrentBatchConfig.transition = null;
setCurrentUpdatePriority(priority);
return flushPassiveEffectsImpl();
}
// root为全局rootWithPendingPassiveEffects
// 调用该useEffect在上一次render时的销毁函数
// 调用该useEffect在本次render时的回调函数
// 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行他
function flushPassiveEffectsImpl() {
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current, lanes, transitions);
flushSyncCallbacks();
}
useEffect 会在组件渲染完成后异步执行,不会阻塞浏览器渲染主线程,因此适合处理一些异步操作(例如发起网络请求、订阅事件等)。
useLayoutEffect 会在组件渲染完成后同步执行,会阻塞浏览器渲染主线程,因此适合处理一些需要同步更新 DOM 的操作(例如计算元素尺寸、设置样式等)。由于它是同步的,可能会导致页面性能受到影响,因此需要谨慎使用。
useMemo
//将回调函数(nextCreate)的执行结果作为value保存
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
if (shouldDoubleInvokeUserFnsInHooksDEV) {
nextCreate();
}
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) {
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
//判断update前后value是否变化
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
if (shouldDoubleInvokeUserFnsInHooksDEV) {
nextCreate();
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
useCallback
//将回调函数作为value保存
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];
// 判断update前后value是否变化
if (areHookInputsEqual(nextDeps, prevDeps)) {
//若没有变化怎返回之前的值
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
useMemo()
缓存的是一个值,而 useCallback()
缓存的是一个函数。
小结
在具体实现上,React 使用了链表数据结构和 fiber 架构来优化组件的渲染和更新过程,Hooks 在此基础上进一步提高了组件的可复用性和灵活性。如果你想深入了解 Hooks 的实现原理,可以查看 React 的源代码。
转载自:https://juejin.cn/post/7219129726078533693