likes
comments
collection
share

React Hooks原理解析

作者站长头像
站长
· 阅读数 80

这里先简单列举我们常用的几种hooks,不熟悉的同学建议去官网看下,自己实践下。

React 常用的 hooks 包括:

  1. useState:用于在函数组件中添加状态。
  2. useEffect:允许在每次渲染后执行副作用操作,如订阅和取消订阅事件、更新DOM等。
  3. useContext:用于在组件之间共享数据,而不必通过 props 层层传递。
  4. useReducer:将多个状态相关的操作归纳到一个 reducer 函数中,类似于 Redux 的reducer。
  5. useCallback:返回一个记忆化的函数引用,以避免在每次渲染时重新创建该函数。
  6. useMemo:允许缓存计算结果,只有在依赖项发生变化时才会重新计算。
  7. useRef:返回一个可变的 ref 对象,可以用来保存任何可变值,而且不会触发组件重新渲染。
  8. useLayoutEffect:与 useEffect 类似,但是它会在页面布局完成后同步执行,可以获取最新的 DOM 布局信息。
  9. useImperativeHandle: 在使用 ref 时,允许自定义暴露给父组件的实例值。
  10. useDebugValue:可以为自定义 hook 提供调试信息,在 React 开发者工具中显示。

Hooks原理实现

流程图如下:renderWithHooks 根据current来判断当前是首次渲染还是更新。 hooks加载时调用对应的mount函数,更新时调用对应的update函数。 hooks生成单向链表,通过next连接,最后一个next指向null。 state hooks会生成update循环链表, effects会生成另外一个effectList循环链表。

React Hooks原理解析

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;
}

HooksDispatcherOnMountHooksDispatcherOnUpdate对象分别存放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);
}

React Hooks原理解析



/**
 * 构建新的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();
}

React Hooks原理解析

React Hooks原理解析

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
评论
请登录