React19源码系列之Hooks(useId)

useId的介绍

https://zh-hans.react.dev/reference/react/useId

useId 是 React 18 引入的一个新 Hook,主要用于生成全局唯一的 ID。在开发中,我们经常需要为元素(如表单元素、模态框等)生成唯一 ID,以便在 JavaScript 中进行操作或在 CSS 中进行样式绑定。使用 useId 可以避免手动管理 ID 带来的问题,比如在服务器端渲染(SSR)时客户端和服务器端生成的 ID 不一致。

useId的使用

例子:基本用法

import React, { useId } from 'react';

function App() {
    const id = useId();
    return (
        <div>
            <label htmlFor={id}>用户名:</label>
            <input type="text" id={id} />
        </div>
    );
}

export default App;

例子:处理多个ID

import React, { useId } from 'react';

function App() {
    const baseId = useId();
    const nameId = `${baseId}-name`;
    const emailId = `${baseId}-email`;

    return (
        <form>
            <label htmlFor={nameId}>姓名:</label>
            <input type="text" id={nameId} />
            <br />
            <label htmlFor={emailId}>邮箱:</label>
            <input type="email" id={emailId} />
        </form>
    );
}

export default App;

例子:在组件树中使用
useId 在组件树中使用时,每个组件实例都会生成不同的 ID。这意味着即使在嵌套组件中多次使用 useId,也不会产生冲突

import React, { useId } from 'react';

function InputWithLabel({ labelText }) {
    const id = useId();
    return (
        <div>
            <label htmlFor={id}>{labelText}</label>
            <input type="text" id={id} />
        </div>
    );
}

function App() {
    return (
        <div>
            <InputWithLabel labelText="用户名" />
            <InputWithLabel labelText="密码" />
        </div>
    );
}

export default App;

hook 初始化入口

hook是函数组件中的钩子函数。在函数组件渲染的过程中会调用renderWithHooks函数。

renderWithHooks

函数参数含义:

  • current:旧的 Fiber 节点,如果是首次渲染则为 null。
  • workInProgress:当前正在处理的新 Fiber 节点。
  • Component:要渲染的函数式组件,它接收 props 和 secondArg 作为参数,并返回 JSX 元素。
  • props:传递给组件的属性。
  • secondArg:额外的参数。
  • nextRenderLanes:下一次渲染的优先级车道。
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,表示当前正在渲染这个 Fiber 节点。
  currentlyRenderingFiber = workInProgress;

  // 重置信息
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  //钩子调度器,区分是初始化还是更新
  ReactSharedInternals.H =
    current === null || current.memoizedState === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;

  //调用函数组件
  let children = Component(props, secondArg);

  // 调用 finishRenderingHooks 函数,进行一些渲染完成后的清理和处理工作,例如处理副作用钩子的执行等。
  finishRenderingHooks(current, workInProgress, Component);

  return children;
}
// renderLanes:表示当前渲染的优先级车道,初始值为 NoLanes(通常为 0),用于标识渲染任务的优先级。
let renderLanes: Lanes = NoLanes;//默认0

// 指向当前正在渲染的 Fiber 节点,初始为 null。Fiber 是 React 中的一种数据结构,用于表示组件树中的每个节点。
let currentlyRenderingFiber: Fiber = (null: any);

// 当前的钩子
let currentHook: Hook | null = null;

//正在处理的钩子
let workInProgressHook: Hook | null = null;

所以初始化用HooksDispatcherOnMount,更新使用HooksDispatcherOnUpdate

HooksDispatcherOnMount

const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  use,
  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,
  useSyncExternalStore: mountSyncExternalStore,
  useId: mountId,
};

HooksDispatcherOnUpdate

const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,
  use,
  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,
  useSyncExternalStore: updateSyncExternalStore,
  useId: updateId,
};

useId初始化

function useId(): string {
//resolveDispatcher 是 React 内部的一个函数,它的作用是获取当前的调度器(dispatcher)
  const dispatcher = resolveDispatcher();
 // dispatcher.useId() 调用了调度器对象的 useId 方法。这个方法会生成一个全局唯一的 ID 字符串,并将其返回。
  return dispatcher.useId();
}

resolveDispatcher

function resolveDispatcher() {
  // 从 ReactSharedInternals 对象中获取名为 H 的属性,并将其赋值给变量 dispatcher。ReactSharedInternals 是 React 内部使用的一个共享对象,它包含了一些 React 运行时的关键信息和工具。H 属性在这里代表着当前 React 环境下的调度器实例。
  const dispatcher = ReactSharedInternals.H;
  // 将获取到的调度器实例 dispatcher 返回给调用者。这样,调用 resolveDispatcher 函数的代码就可以使用这个调度器来执行各种调度相关的操作,比如调用调度器的 useState 方法来处理状态管理。
  return dispatcher;
}

mountId

mountId 函数是 React 中 useId Hook 在挂载阶段(组件首次渲染)用于生成全局唯一 ID 的核心实现。该函数会根据当前是服务端渲染(SSR)还是客户端渲染的不同情况,生成不同格式的唯一 ID,并将其存储在 Fiber 节点对应的 hook 对象的 memoizedState 属性上,最后返回这个唯一 ID。

function mountId(): string {
  //创建 hook 对象,将 hook 对象添加到 workInProgressHook 单向链表中,返回最新的 hook 链表
  const hook = mountWorkInProgressHook();
//getWorkInProgressRoot() 方法获取当前的 FiberRoot 对象
  const root = ((getWorkInProgressRoot(): any): FiberRoot);
  //从 FiberRoot 对象 上获取id前缀
  const identifierPrefix = root.identifierPrefix;

  let id;
  // 调用 getIsHydrating() 方法判断是服务端渲染还是客户端渲染
  if (getIsHydrating()) {
    //服务端渲染注水(hydrate)阶段生成的唯一 id,以冒号开头,并以冒号结尾,使用大写字母 R 标识该id是服务端渲染生成的id
    //获取组件树的id
    const treeId = getTreeId();
    // Use a captial R prefix for server-generated ids.
    id = ':' + identifierPrefix + 'R' + treeId;

    // localIdCounter 变量记录组件中 useId 的执行次数
    const localId = localIdCounter++;
    if (localId > 0) {
      id += 'H' + localId.toString(32);
    }

    id += ':';
  } else {
    //客户端渲染生成的唯一 id,以冒号开头,并以冒号结尾,使用小写字母 r 标识该id 是客户端渲染生成的id
    //全局变量 globalClientIdCounter 记录 useId hook 在组件中的调用次数
    const globalClientId = globalClientIdCounter++;// globalClientIdCounter初始值为0
    id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
  }
  // 将生成的唯一 id 存储到 hook 对象的 memoizedState 属性上
  hook.memoizedState = id;
  return id;
}

以下代码可以看到useId的返回值的格式

id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
// ':r0:'

如何在挂载的时候加入配置项identifierPrefix

ReactDOM.createRoot(document.getElementById('box'),{
  identifierPrefix: 'testyoyo'
});
id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
//:testyoyor0:

updateId

当函数组件刷新时,重新构建hook链表时,遇到useId会执行以下代码

function updateId(): string {
  //  获取当前的 workInProgressHook
  const hook = updateWorkInProgressHook();
  // 从当前的 workInProgressHook 上获取 useId 生成的 id,因此即使组件重新渲染,id 也不会变化
  const id: string = hook.memoizedState;
  return id;
}

工具函数 mountWorkInProgressHook

mountWorkInProgressHook 函数主要用于在 React 的渲染过程中,为当前正在处理的 Fiber 节点(currentlyRenderingFiber)挂载一个新的 Hook。
Hook 是 React 中用于在函数组件中使用状态和副作用的机制,这个函数负责初始化 Hook 对象,并将其添加到当前 Fiber 节点的 Hook 链表中。

function mountWorkInProgressHook(): Hook {
  
  // 初始化hook对象
  const hook: Hook = {
    memoizedState: null,//用于存储当前 Hook 的状态值,在使用 useState 或 useReducer 等钩子时会更新这个值。
    baseState: null,// 表示状态的基础值,在处理更新队列时会用到。
    baseQueue: null,// 存储尚未处理的更新队列,通常与 baseState 配合使用。
    queue: null,// 存储当前 Hook 的更新队列,用于存储状态更新的操作。
    next: null,// 用于将多个 Hook 对象连接成一个链表,指向下一个 Hook 对象。
  };

  // 如果 workInProgressHook 为 null,说明这是当前 Fiber 节点的第一个 Hook。
  if (workInProgressHook === null) {
 // 将 currentlyRenderingFiber 的 memoizedState 属性设置为新创建的 Hook 对象,并将 workInProgressHook 也指向这个 Hook 对象。这样,currentlyRenderingFiber 的 memoizedState 就成为了 Hook 链表的头节点。
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    // 如果 workInProgressHook 不为 null,说明当前 Fiber 节点已经有其他 Hook 存在。此时,将新创建的 Hook 对象添加到 Hook 链表的末尾。
    workInProgressHook = workInProgressHook.next = hook;
  }
  // 返回当前正在处理的 Hook 对象
  return workInProgressHook;
}

在这里插入图片描述

export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
};

当前Fiber 节点与 hook 链表的关联关系图

在这里插入图片描述

工具函数 updateWorkInProgressHook

updateWorkInProgressHook 函数是 React 内部用于更新和管理 Hook 的核心函数之一。在 React 的更新过程中,组件可能会重新渲染,而 Hook 也需要相应地更新。这个函数的主要目的是在新旧 Fiber 节点的 Hook 链表之间进行同步和复用,确保 Hook 的状态能够正确地传递和更新。

function updateWorkInProgressHook(): Hook {

  // 确定下一个当前 Hook(nextCurrentHook)
  let nextCurrentHook: null | Hook;
  
  // currentHook表示当前正在处理的旧 Hook
  // 当 currentHook 为 null 时,说明是处理第一个 Hook。
  if (currentHook === null) {
    // currentlyRenderingFiber.alternate 指向的是上一次渲染的 Fiber 节点(双缓冲机制中的旧 Fiber)
    const current = currentlyRenderingFiber.alternate;

    // 如果该旧 Fiber 存在,就从其 memoizedState 中获取第一个旧 Hook;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;// fiber存在的hookl链表
    } else {
      
      // 若不存在,则 nextCurrentHook 为 null。
      nextCurrentHook = null;
    }
    
  } else {
    // 当 currentHook 不为 null 时,nextCurrentHook 就是当前旧 Hook 的下一个 Hook。
    nextCurrentHook = currentHook.next;
  }

  
  // 确定下一个工作中的 Hook(nextWorkInProgressHook)
  let nextWorkInProgressHook: null | Hook;

   // workInProgressHook 表示当前正在构建的新 Fiber 节点中的 Hook。
  if (workInProgressHook === null) {
     // 若 workInProgressHook 为 null,说明是处理第一个 Hook,则从当前正在渲染的 Fiber 节点的 memoizedState 中获取第一个 Hook 作为 nextWorkInProgressHook
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
     // 若不为 null,则 nextWorkInProgressHook 就是当前 workInProgressHook 的下一个 Hook。
    nextWorkInProgressHook = workInProgressHook.next;
  }



  // 若 nextWorkInProgressHook 不为 null,说明已经有正在构建的 Hook 可以复用。
  if (nextWorkInProgressHook !== null) {

    // 将 workInProgressHook 更新为 nextWorkInProgressHook,并更新 nextWorkInProgressHook 为其下一个 Hook。
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    // 同时,将 currentHook 更新为 nextCurrentHook。
    currentHook = nextCurrentHook;
  } else {
    
// 若 nextWorkInProgressHook 为 null,说明没有现成的工作中的 Hook 可以复用,需要克隆旧 Hook 到新 Hook。
    if (nextCurrentHook === null) {
      const currentFiber = currentlyRenderingFiber.alternate;
    }
// 先将 currentHook 更新为 nextCurrentHook。
    currentHook = nextCurrentHook;

    // 创建一个新的 Hook 对象 newHook,将旧 Hook 的 memoizedState、baseState、baseQueue 和 queue 复制到新 Hook 中。
    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };

    // 如果 workInProgressHook 为 null,说明这是 Hook 链表的第一个 Hook
    if (workInProgressHook === null) {
      // This is the first hook in the list.
      // 将 currentlyRenderingFiber.memoizedState 和 workInProgressHook 都指向新 Hook;
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      // 若不为 null,则将新 Hook 添加到 Hook 链表的末尾。
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }

  // 返回当前工作中的 Hook
  return workInProgressHook;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值