476 字
2 分钟
useId

为什么需要它#

无障碍属性靠 id 把两个元素关联起来:

<label>
密码:
<input type="password" aria-describedby="password-hint" />
</label>
<p id="password-hint">密码至少 18 位</p>

这样写死在 React 里会出事:同一个组件在页面上渲染两次,id 就重复了,aria-describedby 指向哪个也说不清。

用 useId 生成#

import { useId } from 'react'
function PasswordField() {
const passwordHintId = useId()
return (
<>
<label>
密码:
<input type="password" aria-describedby={passwordHintId} />
</label>
<p id={passwordHintId}>密码至少 18 位</p>
</>
)
}

组件渲染几次,就得到几组互不重复的 id。它不接受参数,返回一个字符串。

一组相关元素共用一个前缀#

不必给每个元素都调一次,生成一个前缀再拼接:

const id = useId()
<label htmlFor={id + '-firstName'}>名字:</label>
<input id={id + '-firstName'} />
<label htmlFor={id + '-lastName'}>姓氏:</label>
<input id={id + '-lastName'} />

服务端渲染要求两棵树一致#

useId 的值由组件在树里的位置算出来,所以服务端与客户端渲染出的组件树必须完全一致,否则两端算出的 id 对不上,水合会报不匹配。

页面里有多个独立的 React 应用时,给 createRoot 或 hydrateRoot 传 identifierPrefix,避免两个应用生成的 id 撞车。这些应用里如果有服务端渲染的,服务端与客户端要传同一个前缀。

19.2 改了默认前缀#

默认前缀从 :r: 改成了 _r_(React 19.2),目的是让 id 的形态符合规范。这条的实用含义是:别把 id 的具体字符串写进测试断言或 CSS 选择器,它不是稳定接口。

三件不要做的事#

别用 useId 生成列表的 key。key 要从你自己的数据里来:

// 不行
{list.map(item => <li key={useId()}>{item.name}</li>)}
// 可以
{list.map(item => <li key={item.id}>{item.name}</li>)}

别拿它当 use() 的缓存键。它在组件挂载期间稳定,但渲染过程中可能变化。

它不能用在异步的 Server Component 里。

第 25 篇,共 39 篇本文属于系列

useId
https://www.choria.top/posts/hook-use-id/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时