useId
是一个 React Hook,可以生成传递给可访问性属性的唯一 ID。
const id = useId()
参考
useId()
在组件的顶层调用 useId
以生成唯一 ID:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
参数
useId
不带任何参数。
返回值
useId
返回一个唯一的字符串 ID,与此特定组件中的 useId
调用相关联。
注意事项
-
useId
是一个 Hook,因此你只能在组件的顶层或自己的 Hooks 中调用它。 你不能在内部循环或条件判断中调用它。如果需要,可以提取一个新组件并将 State 移到该组件中。 -
useId
不应该被用来生成列表中的 Keys。 Keys 应该由你的数据生成。
用法
为可访问性属性生成唯一 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>
密码:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
密码应该包含至少18个字符
</p>
然而,在 React 中硬编码 ID 并不是一个好的实践。 一个组件可能会在页面上渲染多次,但是 ID 必须是唯一的!不要硬编码 ID,而是使用 useId
生成唯一的 ID。
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
密码:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
密码应该包含至少18个字符
</p>
</>
);
}
现在,即使 PasswordField
多次出现在屏幕上,生成的 ID 并不会冲突。
import { useId } from 'react'; function PasswordField() { const passwordHintId = useId(); return ( <> <label> 密码: <input type="password" aria-describedby={passwordHintId} /> </label> <p id={passwordHintId}> 密码应该包含至少18个字符 </p> </> ); } export default function App() { return ( <> <h2>输入密码</h2> <PasswordField /> <h2>验证密码</h2> <PasswordField /> </> ); }
请看这个视频, 了解辅助技术所提供的用户体验的差异。
Deep Dive
你可能想知道为什么使用 useId
比增加全局变量(如 nextId ++)更好。
useId
的主要好处是 React 确保它能够与服务器渲染一起工作。 在服务器渲染期间,你的组件生成 HTML 输出。随后,在客户端 Hydration 会将你的事件处理程序附加到生成的 HTML 上。Hydration 工作, 客户端必须匹配服务器的 HTML 输出。
使用递增计数器非常难以保证这一点,因为客户端组件被 Hydrated 的顺序可能与服务器HTML发出的顺序不匹配。通过调用 useId
,你可以确保 Hydration 正常工作,并且服务器和客户端之间的输出将匹配。
在React内部,useId
是从调用组件的 “ 父路径 ” 生成的。这就是为什么,如果客户端和服务器树相同,则 “ 父路径 ” 将匹配,而不管渲染顺序如何的原因。
为多个相关元素生成 ID
如果你需要为多个相关元素生成 ID,可以调用 useId
来为它们生成共同的前缀:
import { useId } from 'react'; export default function Form() { const id = useId(); return ( <form> <label htmlFor={id + '-firstName'}>名字:</label> <input id={id + '-firstName'} type="text" /> <hr /> <label htmlFor={id + '-lastName'}>姓氏:</label> <input id={id + '-lastName'} type="text" /> </form> ); }
这使你避免为每个需要唯一 ID 的元素调用useId
。
为所有生成的 ID 指定共享前缀
如果你在单个页面上渲染多个独立的 React 应用程序,请在 createRoot
或 hydrateRoot
调用中将 identifierPrefix
作为选项传递。这确保了由两个不同应用程序生成的 ID 永远不会冲突,因为使用 useId
生成的每个 ID 都将以你指定的不同前缀开头。
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 />);