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,因此你只能在组件的顶层或你自己的 Hooks 中调用它。你不能在循环或条件内调用它。如果你需要这样做,请提取一个新组件并将状态移入其中。 -
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
比递增全局变量(例如 nextId++
)更好。
useId
的主要好处是 React 确保它与 服务器端渲染 兼容。在服务器端渲染期间,您的组件会生成 HTML 输出。之后,在客户端,水合 会将您的事件处理程序附加到生成的 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
或 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
调用的identifierPrefix
与传递给服务器端API(例如renderToPipeableStream
)的identifierPrefix
相同。服务器端 API
// 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
。