React Hooks源码深度解析

react,hooks,源码,深度,解析 · 浏览次数 : 26

小编点评

**React Hooks 的实现原理** React Hooks 是 React16.8 引入的一个新特性,允许函数组件使用 state 和其他 React 特性,而不必使用类组件。 Hooks 是 React 内部的一个重要机制,它负责管理组件中的所有 Hook,并确保它们在组件渲染期间以正确的顺序调用。 **React 内部 Hook 管理器** React 内部有一个称为 Hook 的管理器,负责管理组件中的所有 Hook。该管理器包含以下两个属性: * `queue`: 存储组件中所有 Hook 状态和更新函数的数组。 * `current`: 存储当前正在渲染的组件的 Hook链表。 **Hooks 的实现** * `useState` Hook 创建一个新的 Hook 对象,并将其添加到 `queue` 中。 * `useEffect` Hook 在组件渲染完成后调用 `scheduleWork` 方法,这是一个异步函数,它用来通知 React调度器有任务需要执行。 * `useReducer` Hook 使用 `updateReducer` 函数来管理组件状态。 * `useEffect` Hook 在组件被卸载时清除当前 effect 的状态信息。 **Hooks 的核心功能** * **状态管理:** Hooks 可以用来管理组件中的状态。 * **副作用处理:** Hooks 可以用来处理组件中的副作用操作,例如访问远程数据。 * **生命周期管理:** Hooks 可以帮助我们管理组件的生命周期事件,例如当组件被创建或更新时执行回调函数。 **总结** React Hooks 是一个非常重要的概念,它提供了更简单、更易于理解的React开发体验。 Hooks 可以帮助我们轻松地管理组件状态和处理副作用,从而提高代码的维护性和可读性。

正文

作者:京东零售 郑炳懿

前言

React HooksReact16.8 引入的一个新特性,它允许函数组件中使用state和其他 React 特性,而不必使用类组件。Hooks是一个非常重要的概念,因为它们提供了更简单、更易于理解的React开发体验。

React Hooks的核心源码主要包括两个部分:React内部的Hook管理器和一系列预置的Hook函数

首先,让我们看一下React内部的Hook管理器。这个管理器是React内部的一个重要机制,它负责管理组件中的所有Hook,并确保它们在组件渲染期间以正确的顺序调用。

内部Hook管理器

示例:

const Hook = {
  queue: [],
  current: null,
};

function useState(initialState) {
  const state = Hook.current[Hook.queue.length];
  if (!state) {
    Hook.queue.push({
      state: typeof initialState === 'function' ? initialState() : initialState,
      setState(value) {
        this.state = value;
        render();
      },
    });
  }
  return [state.state, state.setState.bind(state)];
}

function useHook(callback) {
  Hook.current = {
    __proto__: Hook.current,
  };
  try {
    callback();
  } finally {
    Hook.current = Hook.current.__proto__;
  }
}

function render() {
  useHook(() => {
    const [count, setCount] = useState(0);
    console.log('count:', count);
    setTimeout(() => {
      setCount(count + 1);
    }, 1000);
  });
}

render();

在这个示例中,Hook对象有两个重要属性:queuecurrentqueue存储组件中所有Hook的状态和更新函数,current存储当前正在渲染的组件的Hook链表。useStateuseHook函数则分别负责创建新的Hook状态和在组件中使用Hook

预置 Hook 函数

useState Hook

以下是useState Hook的实现示例:

function useState(initialState) {
  const hook = updateWorkInProgressHook();
  if (!hook.memoizedState) {
    hook.memoizedState = [
      typeof initialState === 'function' ? initialState() : initialState,
      action => {
        hook.queue.pending = true;
        hook.queue.dispatch = action;
        scheduleWork();
      },
    ];
  }
  return hook.memoizedState;
}

上述代码实现了useState Hook,其主要作用是返回一个state和更新函数的数组,state 初始值为initialState

在这个实现中,updateWorkInProgressHook()函数用来获取当前正在执行的函数组件的 fiber 对象并判断是否存在对应的hook。它的实现如下:

function updateWorkInProgressHook() {
  const fiber = getWorkInProgressFiber();
  let hook = fiber.memoizedState;
  if (hook) {
    fiber.memoizedState = hook.next;
    hook.next = null;
  } else {
    hook = {
      memoizedState: null,
      queue: {
        pending: null,
        dispatch: null,
        last: null,
      },
      next: null,
    };
  }
  workInProgressHook = hook;
  return hook;
}

getWorkInProgressFiber()函数用来获取当前正在执行的函数组件的fiber对象,workInProgressHook则用来存储当前正在执行的hook对象。在函数组件中,每一个useState调用都会创建一个新的 hook 对象,并将其添加到fiber对象的hooks链表中。这个hooks链表是通过fiber对象的memoizedState属性来维护的。

我们还需要注意到在useState Hook的实现中,每一个hook对象都包含了一个queue对象,用来存储待更新的状态以及更新函数。scheduleWork()函数则用来通知React调度器有任务需要执行。

React的源码中,useState函数实际上是一个叫做useStateImpl的内部函数。

下面是useStateImpl的源码:

function useStateImpl<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

可以看到,useStateImpl函数的作用就是获取当前的dispatcher并调用它的useState方法,返回一个数组,第一个元素是状态的值,第二个元素是一个dispatch函数,用来更新状态。这里的resolveDispatcher函数用来获取当前的dispatcher,其实现如下:

function resolveDispatcher(): Dispatcher {
  const dispatcher = currentlyRenderingFiber?.dispatcher;
  if (dispatcher === undefined) {
    throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)');
  }
  return dispatcher;
}

resolveDispatcher函数首先尝试获取当前正在渲染的fiber对象的dispatcher属性,如果获取不到则说

明当前不在组件的渲染过程中,就会抛出一个错误。

最后,我们来看一下useState方法在具体的dispatcher实现中是如何实现的。我们以useReducer

dispatcher为例,它的实现如下:

export function useReducer<S, A>(
  reducer: (prevState: S, action: A) => S,
  initialState: S,
  initialAction?: A,
): [S, Dispatch<A>] {
  const [dispatch, currentState] = updateReducer<S, A>(
    reducer,
    // $FlowFixMe: Flow doesn't like mixed types
    [initialState, initialAction],
    // $FlowFixMe: Flow doesn't like mixed types
    reducer === basicStateReducer ? basicStateReducer : updateStateReducer,
  );
  return [currentState, dispatch];
}

可以看到,useReducer方法实际上是调用了一个叫做updateReducer的函数,返回了一个包含当前状态和dispatch函数的数组。updateReducer的实现比较复杂,涉及到了很多细节,这里不再展开介绍。

useEffect Hook

useEffectReact中常用的一个Hook函数,用于在组件中执行副作用操作,例如访问远程数据、添加/移除事件监听器、手动操作DOM等等。useEffect的核心功能是在组件的渲染过程结束之后异步执行回调函数,它的实现方式涉及到 React 中的异步渲染机制。

以下是useEffect Hook的实现示例:

function useEffect(callback, dependencies) {
  // 通过调用 useLayoutEffect 或者 useEffect 方法来获取当前的渲染批次
  const batch = useContext(BatchContext);

  // 根据当前的渲染批次判断是否需要执行回调函数
  if (shouldFireEffect(batch, dependencies)) {
    callback();
  }

  // 在组件被卸载时清除当前 effect 的状态信息
  return () => clearEffect(batch);
}

在这个示例中,useEffect接收两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,

React会在下一次渲染时重新执行useEffect中传入的回调函数。

useEffect函数的实现方式主要依赖于React中的异步渲染机制。当一个组件需要重新渲染时,React会将所有的state更新操作加入到一个队列中,在当前渲染批次结束之后再异步执行这些更新操作,从而避免在同一个渲染批次中连续执行多次更新操作。

useEffect函数中,我们通过调用useContext(BatchContext)方法来获取当前的渲染批次,并根据shouldFireEffect方法判断是否需要执行回调函数。在回调函数执行完毕后,我们需要通过clearEffect方法来清除当前effect的状态信息,避免对后续的渲染批次产生影响。

总结

总的来说,React Hooks的实现原理并不复杂,它主要依赖于React内部的fiber数据结构和调度系统,通过这些机制来实现对组件状态的管理和更新。Hooks能够让我们在函数组件中使用状态和其他React特性,使得函数组件的功能可以和类组件媲美。

除了useStateuseEffecthookReact还有useContext等常用的Hook。它们的实现原理也基本相似,都是利用fiber架构来实现状态管理和生命周期钩子等功能。

以上是hook简单实现示例,它们并不是React中实际使用的代码,但是可以帮助我们更好地理解hook的核心实现方式。

与React Hooks源码深度解析相似的内容:

React Hooks源码深度解析

React Hooks是React16.8 引入的一个新特性,它允许函数组件中使用state和其他 React 特性,而不必使用类组件。Hooks是一个非常重要的概念,因为它们提供了更简单、更易于理解的React开发体验。本篇文章以React Hooks源码为基,进行深度解析复盘其实现原理。

React闭包陷阱

# React闭包陷阱 `React Hooks`是`React 16.8`引入的一个新特性,其出现让`React`的函数组件也能够拥有状态和生命周期方法,其优势在于可以让我们在不编写类组件的情况下,更细粒度地复用状态逻辑和副作用代码,但是同时也带来了额外的心智负担,闭包陷阱就是其中之一。 ## 闭

Hooks与事件绑定

Hooks与事件绑定 在React中,我们经常需要为组件添加事件处理函数,例如处理表单提交、处理点击事件等。通常情况下,我们需要在类组件中使用this关键字来绑定事件处理函数的上下文,以便在函数中使用组件的实例属性和方法。React Hooks是React 16.8引入的一个新特性,其出现让Reac

两张图带你全面了解React状态管理库:zustand和jotai

zustand 和 jotai 是当下比较流行的react状态管理库。其都有着轻量、方便使用,和react hooks能够很好的搭配,并且性能方面,对比React自身提供的context要好得多,因此被很多开发小伙伴所喜爱。 更有意思的是,这两个库的作者是同一个人,同时他还开源了另外一个状态库 va

如何在2023年学习React

在2023年学习React并不是一件容易的事情。自2019年React Hooks发布以来,我们已经拥有了很多稳定性,但现在形势正在再次变化。而这次变化可能比使用React Hooks时更加不稳定。在本文中,我想比较两种学习React的方式:以库的方式和以框架的方式。 为了让事情更加清晰:React

antd 3.x升4.x踩坑之路~

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 兼容性问题 第三方依赖兼容问题 React - 最低 v16.9,部分组件使用 hooks 重构 react升级相关文档 Less - 最低 v3.1.0,建议升级到

我的 React 最佳实践

There are a thousand Hamlets in a thousand people's eyes. 威廉·莎士比亚 免责声明:以下充满个人观点,辩证学习 React 目前开发以函数组件为主,辅以 hooks 实现大部分的页面逻辑。目前数栈的 react 版本是 16.13.1,该版本

深入理解 React 的 useSyncExternalStore Hook

深入理解 React 的 useSyncExternalStore Hook 大家好,今天我们来聊聊 React 18 引入的一个新 Hook:useSyncExternalStore。这个 Hook 主要用于与外部存储同步状态,特别是在需要确保状态一致性的场景下非常有用。本文将深入探讨这个 Hoo

日常工作中需要避免的9个React坏习惯

前言 React是前端开发领域中最受欢迎的JavaScript库之一,但有时候在编写React应用程序时,可能陷入一些不佳的习惯和错误做法。这些不佳的习惯可能导致性能下降、代码难以维护,以及其他问题。在本文中,我们将探讨日常工作中应该避免的9个坏React习惯,并提供相关示例代码来说明这些问题以及如

基于ReAct机制的AI Agent

当前,在各个大厂纷纷卷LLM的情况下,各自都借助自己的LLM推出了自己的AI Agent,比如字节的Coze,百度的千帆等,还有开源的Dify。你是否想知道其中的原理?是否想过自己如何实现一套AI Agent?当然,借助LangChain就可以。