useId 是一个 React Hook,用于生成可传递给无障碍属性的唯一 ID。

const id = useId()

参考

useId()

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

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

请参阅下面的更多示例。

参数

useId 不接受任何参数。

返回值

useId 返回与此特定组件中的此特定 useId 调用关联的唯一 ID 字符串。

注意事项

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

  • 不应使用 useId 在列表中生成键应从数据生成键。


用法

陷阱

不要调用 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,允许您指定两个标签彼此相关。例如,您可以指定一个元素(如输入框)由另一个元素(如段落)描述。

在常规 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 必须是唯一的!不要硬编码 ID,而是使用 useId 生成唯一 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 也不会冲突。

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

export default function App() {
  return (
    <>
      <h2>Choose password</h2>
      <PasswordField />
      <h2>Confirm password</h2>
      <PasswordField />
    </>
  );
}

观看此视频,了解使用辅助技术的用户体验差异。

陷阱

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

深入探讨

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

您可能想知道为什么 useId 比递增全局变量(如 nextId++)更好。

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

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

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


如果需要为多个相关元素提供 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 作为选项传递给您的 createRoothydrateRoot 调用。这可以确保两个不同应用程序生成的 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 调用的 identifierPrefix 与传递给 服务器 API(如 renderToPipeableStream)的 identifierPrefix 相同。

// 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