06-React hooks 源码分析

本文详细探讨了React Hooks的引入原因,如解决组件间状态复用的难题,以及它如何简化复杂组件的理解。文章通过源码分析解释了Hooks的工作流程,包括创建Hook链表、管理Hook数据结构、不同阶段的Dispatcher以及如何更新组件状态。此外,还对比了自定义实现的useState与官方实现的差异,并展示了useState与useReducer的执行过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么需要 hook

React 文档中也有说明了 Hooks 的提出主要是为了解决什么问题的:

组件之间复用状态逻辑很难。

就以前 React 为了将一个组件的逻辑抽离复用,不和渲染代码混用在一个 class 的做法,比较推介的是用高阶组件,将状态逻辑抽离出来,通过不同的样式组件传入带有状态逻辑的高阶组件中,增强样式组件的功能,从而达到复用逻辑的功能。再早期就会使用 mixin 去实现。

缺点:增加嵌套层级,代码会更加难以理解。

复杂组件变得难以理解

在使用 class 组件的时候,我们少不了在生命周期函数中添加一些操作,例如调用一些函数,或者去发起请求,在销毁组件的时候为了防止内存溢出,我们可能还需要对一些事件移除。那么这个时候我们就需要在componentDidMount,componentDidUpdate 中可能会调用相同的函数获取数据,componentWillUnmount 中移除事件等;这些因为和组件有很强的耦合性,也很难通过高阶组件的方式抽离出来,而 Hook 将组件中关联的部分拆分成更小的函数,每个函数功能更加单一。

难以理解的 class

Hook 就是一个以纯函数的方式存在的class组件;以前我们使用纯函数组件时都有一个标准,就是这个组件并不具备自身的生命周期使用,以及自己独立的state。只是单纯的返回一个组件。或者是根据传入的 props 组装组件。但随着 Hook 的发布,React 团队是想将 React 更加偏向函数式编程的方式编写组件,让本来存函数组件变得可以使用 class 组件的一些特性。

hook 调用入口

在 hook 源码中 hook 存在于 Dispatcher 中,Dispatcher 就是一个对象,不同 hook 调用的函数不一样;在 renderWithHooks 函数中,在 FunctionComponent render 前, 会根据 FunctionComponent 对应 fiber 的以下条件区分 mount 与 update。

current === null || current.memoizedState === null

并将不同情况对应的dispatcher赋值给全局变量ReactCurrentDispatcher的current属性。

export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
   
   
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;

  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
}

// mount 时的 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,
  ...
}

// mount 时的 Dispatcher
const HooksDispatcherOnUpdate: Dispatcher = {
   
   
  readContext,

  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useInsertionEffect: updateInsertionEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  ...
};

在 FunctionComponent render 时,会从 ReactCurrentDispatcher.current(即当前 dispatcher)中寻找需要的hook。换言之,不同的调用栈上下文为 ReactCurrentDispatcher.current 赋值不同的 dispatcher,则FunctionComponent render 时调用的 hook 也是不同的函数。

Hook的数据结构

通过 mountWorkInProgressHook 函数进行创建 hook 的数据结构:

function mountWorkInProgressHook(): Hook {
   
   
  const hook: Hook = {
   
   
    memoizedState: null, // 对于不同 hook,有不同的值

    baseState: null, // 初始 state
    baseQueue: null, // 初始 queue 队列
    queue: null, // 需要更新的 update

    next: null, // 下一个 hook
  };

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

这里除了 memoizedState 字段外,其他字段与之前介绍的 updateQueue 的数据结构字段意义类似。

hook 链表结构

上面创建的 hook 结构中,通过 next 指针,实现了一个无环的单向链表。产生的 hook 对象依次排列,形成链表存储到函数组件 fiber.memoizedState 上。

{
   
   
    baseQueue: null,
    baseState: 'hook1',
    memoizedState: null,
    queue: null,
    next: {
   
   
        baseQueue: null,
        baseState: null,
        memoizedState: 'hook2',
        next: null
        queue: null
    }
}

memoizedState

下面来看下不同的 hook 中 memoizedState 对应的值:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jiegiser#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值