useId
是一个 React 钩子,用于生成可传递给可访问性属性的唯一 ID。
¥useId
is a React Hook for generating unique IDs that can be passed to accessibility attributes.
const id = useId()
参考
¥Reference
useId()
在组件的顶层调用 useId
以生成唯一 ID:
¥Call useId
at the top level of your component to generate a unique ID:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
参数
¥Parameters
useId
没有参数。
¥useId
does not take any parameters.
返回
¥Returns
useId
返回与此特定组件中的此特定 useId
调用关联的唯一 ID 字符串。
¥useId
returns a unique ID string associated with this particular useId
call in this particular component.
注意事项
¥Caveats
-
useId
是一个 Hook,所以你只能在你的组件的顶层或者你自己的钩子中调用它。你不能在循环或条件内调用它。如果需要,提取一个新组件并将状态移入其中。¥
useId
is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it. -
useId
不应该用于生成列表中的键。键应该从你的数据中生成。¥
useId
should not be used to generate keys in a list. Keys should be generated from your data.
用法
¥Usage
为可访问性属性生成唯一 ID
¥Generating unique IDs for accessibility attributes
在组件的顶层调用 useId
以生成唯一 ID:
¥Call useId
at the top level of your component to generate a unique ID:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
然后,你可以将 generated ID 传递给不同的属性:
¥You can then pass the generated ID to different attributes:
<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>
让我们通过一个例子来看看这在什么时候有用。
¥Let’s walk through an example to see when this is useful.
HTML 可访问性属性 和 aria-describedby
一样让你指定两个标签相互关联。例如,你可以指定一个元素(如输入)由另一个元素(如段落)描述。
¥HTML accessibility attributes like aria-describedby
let you specify that two tags are related to each other. For example, you can specify that an element (like an input) is described by another element (like a paragraph).
在常规 HTML 中,你可以这样写:
¥In regular HTML, you would write it like this:
<label>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
The password should contain at least 18 characters
</p>
然而,像这样硬编码 ID 在 React 中并不是一个好习惯。一个组件可能会在页面上渲染多次 - 但 ID 必须是唯一的!不要对 ID 进行硬编码,而是使用 useId
生成一个唯一的 ID:
¥However, hardcoding IDs like this is not a good practice in React. A component may be rendered more than once on the page—but IDs have to be unique! Instead of hardcoding an ID, generate a unique ID with useId
:
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 也不会冲突。
¥Now, even if PasswordField
appears multiple times on the screen, the generated IDs won’t clash.
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 /> </> ); }
看这个视频 以查看辅助技术在用户体验方面的差异。
¥Watch this video to see the difference in the user experience with assistive technologies.
深入研究
¥Why is useId better than an incrementing counter?
你可能想知道为什么 useId
比增加像 nextId++
这样的全局变量更好。
¥You might be wondering why useId
is better than incrementing a global variable like nextId++
.
useId
的主要好处是 React 确保它可以与 服务器渲染。 在服务器渲染期间,你的组件生成 HTML 输出。稍后,在客户端上,hydration 将你的事件处理程序附加到生成的 HTML。要进行水合作用,客户端输出必须与服务器 HTML 相匹配。
¥The primary benefit of useId
is that React ensures that it works with server rendering. During server rendering, your components generate HTML output. Later, on the client, hydration attaches your event handlers to the generated HTML. For hydration to work, the client output must match the server HTML.
使用递增计数器很难保证这一点,因为客户端组件的顺序可能与发出服务器 HTML 的顺序不匹配。通过调用 useId
,你可以确保水合作用,并且输出将在服务器和客户端之间匹配。
¥This is very difficult to guarantee with an incrementing counter because the order in which the Client Components are hydrated may not match the order in which the server HTML was emitted. By calling useId
, you ensure that hydration will work, and the output will match between the server and the client.
在 React 内部,useId
是从调用组件的 “父路径” 生成的。这就是为什么如果客户端和服务器树相同,则无论渲染顺序如何,“父路径” 都会匹配。
¥Inside React, useId
is generated from the “parent path” of the calling component. This is why, if the client and the server tree are the same, the “parent path” will match up regardless of rendering order.
为几个相关元素生成 ID
¥Generating IDs for several related elements
如果需要为多个相关元素赋予 ID,可以调用 useId
为它们生成一个共享前缀:
¥If you need to give IDs to multiple related elements, you can call useId
to generate a shared prefix for them:
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
。
¥This lets you avoid calling useId
for every single element that needs a unique ID.
为所有生成的 ID 指定共享前缀
¥Specifying a shared prefix for all generated IDs
如果你在单个页面上渲染多个独立的 React 应用,请将 identifierPrefix
作为选项传递给你的 createRoot
或 hydrateRoot
调用。这可确保两个不同应用生成的 ID 不会发生冲突,因为使用 useId
生成的每个标识符都将以你指定的不同前缀开头。
¥If you render multiple independent React applications on a single page, pass identifierPrefix
as an option to your createRoot
or hydrateRoot
calls. This ensures that the IDs generated by the two different apps never clash because every identifier generated with useId
will start with the distinct prefix you’ve specified.
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 前缀
¥Using the same ID prefix on the client and the server
如果你 在同一页面上渲染多个独立的 React 应用,并且其中一些应用是服务器渲染的,请确保你传递给客户端的 hydrateRoot
调用的 identifierPrefix
与传递给 服务器 API(例如 renderToPipeableStream
。)的 identifierPrefix
相同
¥If you render multiple independent React apps on the same page, and some of these apps are server-rendered, make sure that the identifierPrefix
you pass to the hydrateRoot
call on the client side is the same as the identifierPrefix
you pass to the server APIs such as 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
。
¥You do not need to pass identifierPrefix
if you only have one React app on the page.