第二章前置知识:2.9 useId 基础知识

179 阅读4分钟

本专栏致力于每周分享一个项目,如果本文对你有帮助的话,欢迎点赞或者关注☘️

React18 源码系列会随着学习 React 源码的实时进度而实时更新:约,两天一小改,五天一大改。

useId是一个 React Hook,用于生成可以传递给可访问性属性的唯一 ID。

const id = useId()

useId不带任何参数。useId返回与此特定组件中的此特定useId调用关联的唯一 ID 字符串。

useId是一个 Hook,因此您只能在组件的顶层或您自己的 Hook 中调用它。您不能在循环或条件内调用它。如果需要,请提取一个新组件并将状态移入其中。useId不应用于生成列表中的键

为辅助功能属性生成唯一 ID

在组件的顶层调用useId来生成唯一的 ID:

import { useId } from 'react';



function PasswordField() {

  const passwordHintId = useId();

  // ...

然后您可以将生成的 ID传递给不同的属性:

<>
  <input type="password" aria-describedby={passwordHintId} />
  <p id={passwordHintId}>
</>

HTML 辅助功能属性(如[aria-describedby](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby)可让您指定两个标签彼此相关。例如,您可以指定一个元素(如输入)由另一个元素(如段落)描述。

在常规 HTML 中,您可以这样编写:

<label>

  Password:

  <input

    type="password"

    aria-describedby="password-hint"

  />

</label>

<p id="password-hint">

  The password should contain at least 18 characters

</p>

然而,在 React 中,像这样硬编码 ID 并不是一个好的做法。一个组件可以在页面上多次呈现,但 ID 必须是唯一的!使用useId生成唯一 ID,而不是硬编码 ID:

import { useId } from 'react';



function PasswordField() {

  const passwordHintId = useId();

  return (

    <>

      <label>

        Password:

        <input

          type="password"

          aria-describedby={passwordHintId}

        />

      </label>

      <p id={passwordHintId}>

        The password should contain at least 18 characters

      </p>

    </>

  );

}

现在,即使PasswordField在屏幕上多次出现,生成的ID 也不会发生冲突。

对于服务器渲染useId需要服务器和客户端上有相同的组件树。如果您在服务器和客户端上渲染的树不完全匹配,则生成的 ID 将不匹配。

为什么 useId 比递增计数器更好

useId的主要好处是 React 确保它可以与服务器渲染配合使用。在服务器渲染期间,您的组件会生成 HTML 输出。随后,在客户端上, Hydration将事件处理程序附加到生成的 HTML。为了使水合作用,客户端输出必须与服务器 HTML 匹配。

使用递增计数器很难保证这一点,因为客户端组件的顺序可能与发出服务器 HTML 的顺序不匹配。通过调用useId ,您可以确保水合作用,并且服务器和客户端之间的输出将匹配。

在 React 内部, useId是从调用组件的“父路径”生成的。这就是为什么,如果客户端和服务器树相同,那么无论渲染顺序如何,“父路径”都会匹配。

为多个相关元素生成 ID

如果需要为多个相关元素提供 ID,可以调用useId为它们生成共享前缀:

import { useId } from 'react';

export default function Form() {
  const id = useId();
  return (
    <form>
      <label htmlFor={id + '-firstName'}>First Name:</label>
      <input id={id + '-firstName'} type="text" />
      <hr />
      <label htmlFor={id + '-lastName'}>Last Name:</label>
      <input id={id + '-lastName'} type="text" />
    </form>
  );
}

这可以让您避免为每个需要唯一 ID 的元素调用useId

为所有生成的 ID 指定共享前缀

如果您在单个页面上渲染多个独立的 React 应用程序,请将identifierPrefix作为选项传递给您的[createRoot](https://react.dev/reference/react-dom/client/createRoot#parameters)[hydrateRoot](https://react.dev/reference/react-dom/client/hydrateRoot)调用。这可确保两个不同应用程序生成的 ID 不会发生冲突,因为使用useId生成的每个标识符都将以您指定的不同前缀开头。

import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

const root1 = createRoot(document.getElementById('root1'), {
  identifierPrefix: 'my-first-app-'
});
root1.render(<App />);

const root2 = createRoot(document.getElementById('root2'), {
  identifierPrefix: 'my-second-app-'
});
root2.render(<App />);

客户端和服务器端使用相同的ID前缀

如果您在同一页面上渲染多个独立的 React 应用程序,并且其中一些应用程序是服务器渲染的,请确保您传递给客户端的[hydrateRoot](https://react.dev/reference/react-dom/client/hydrateRoot)调用identifierPrefix与您传递给服务器 API 的identifierPrefix相同,例如作为[renderToPipeableStream](https://react.dev/reference/react-dom/server/renderToPipeableStream)

// Server

import { renderToPipeableStream } from 'react-dom/server';



const { pipe } = renderToPipeableStream(

  <App />,

  { identifierPrefix: 'react-app1' }

);
// Client

import { hydrateRoot } from 'react-dom/client';



const domNode = document.getElementById('root');

const root = hydrateRoot(

  domNode,

  reactNode,

  { identifierPrefix: 'react-app1' }

);

如果页面上只有一个 React 应用程序,则不需要传递identifierPrefix

参考链接

关于作者

作者:Wandra

内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。

专栏:欢迎关注呀🌹

本专栏致力于每周分享一个项目,如果本文对你有帮助的话,欢迎点赞或者关注☘️