useEffect、useState的实现原理怎么通过自定义hooks来实现useState

165 阅读4分钟

useEffect 的 第二个参数

会对对象进行 浅层 比较来判断是否执行 useEffect 中的副作用代码,不会递归比较对象内部的属性。如果对象的引用没有变化,即便对象内部的属性发生变化,useEffect 也不会重新执行,

  1. 可以考虑使用 useEffect 内部的清理函数来手动检查变化;通过一个中间变量 通过取反,return
  2. 使用useMemo来确保每次渲染都返回一个新的对象

实现原理

useEffect 是 React 中的一个 Hook,用于处理副作用。其实现原理涉及到 React 的渲染流程和生命周期,以及 JavaScript 的异步执行机制。

以下是 useEffect 的基本实现原理:

  1. 调度和执行:

    • 当函数组件渲染时,React 会将 useEffect 中的回调函数添加到一个任务队列中,该队列在渲染结束后被执行。
    • React 通过调度器(Scheduler)来安排副作用的执行。调度器是 React 内部的模块,负责管理任务的执行时机。
  2. 副作用的执行时机:

    • 默认情况下,useEffect 中的副作用会在每次组件渲染之后执行。这意味着它在渲染阶段之后、布局阶段之前执行。
    • 如果 useEffect 的第二个参数是空数组 [],则副作用只会在组件挂载和卸载时执行,相当于类似于类组件的 componentDidMountcomponentWillUnmount
    • 如果 useEffect 的第二个参数包含依赖项数组,它会在依赖项发生变化时执行。
  3. 清理函数:

    • 如果 useEffect 返回一个函数,该函数会在组件卸载或下一次 useEffect 执行前执行,用于清理副作用。
    • 清理函数主要用于取消订阅、清除定时器等清理操作。
  4. 优化:

    • React 会尽量在浏览器空闲时执行副作用,以避免阻塞主线程,提高性能。
    • 通过调度器和调度优先级的概念,React 可以在不同的优先级下调度任务,确保高优先级的任务先执行。

useState的实现原理,怎么通过自定义hooks来实现useState

useState 的实现原理基于 Fiber 架构

  1. Fiber 架构:

    • React 16 引入了 Fiber 架构,用于实现可中断、可恢复的渲染。
    • Fiber 架构中,工作单元(Fiber)表示组件的渲染工作。
  2. Hooks 存储在 Fiber 节点中:

    • 在每个 Fiber 节点上,都有一个 Hooks 链表,用于存储该组件使用的所有 Hooks。
    • useState 使用了这个 Hooks 链表来保存状态。
  3. useState 的具体实现:

    • 当组件渲染时,React 会遍历 Fiber 树,找到当前组件的 Fiber 节点。
    • 在 Fiber 节点上,有一个 Hooks 链表,每个节点都包含了当前组件使用的所有 Hooks 的信息。
    • 当执行到 useState 时,React 会在当前组件的 Fiber 节点上查找是否已经有一个对应的 useState 的 Hook。如果有,就直接使用之前的状态;如果没有,就创建一个新的 Hook 并添加到 Hooks 链表中。
  4. useState 返回值:

    • useState 返回一个包含当前状态和更新状态的数组 [state, setState]
    • state 是当前状态的值,setState 是一个更新状态的函数。
  5. useState 的更新触发重新渲染:

    • 当调用 setState 时,React 会标记组件为“脏”(dirty),表示需要重新渲染。
    • 在下一次渲染时,React 会重新调用组件函数,通过 Hooks 链表恢复之前保存的状态值。
  6. 闭包保存状态:

    • 由于 Hooks 在函数组件中是按顺序调用的,React 通过闭包的方式,保证每次调用 useState 都能正确访问到对应的状态

下面是一个简化的伪代码,用于演示 useState 的实现原理:

let currentComponent; // 当前组件的 Fiber 节点
function useState(initialState) {
  const hooks = currentComponent.memoizedState; // 当前组件的 Hooks 链表

  if (!hooks) {
    // 如果当前组件没有 Hooks 链表,创建一个
    currentComponent.memoizedState = {
      queue: [], // 保存状态更新的队列
      baseState: initialState, // 初始状态值
      baseQueue: null, // 上一次的状态更新队列
    };

    hooks = currentComponent.memoizedState;
  }

  // 获取当前 Hook 的索引
  const hookIndex = hooks.queue.length;

  // 保存状态和更新状态的函数到 Hooks 链表
  const state = hookIndex < hooks.baseQueue.length
    ? hooks.baseQueue[hookIndex]
    : hooks.baseState;

  // 更新状态的函数
  const setState = action => {
    hooks.queue.push(action); // 将状态更新添加到队列
    scheduleWork(currentComponent); // 标记组件为“脏”,准备重新渲染
  };

  return [state, setState];
}