React useId 解决了什么问题?
简化版
useId 用来生成稳定且 SSR 友好的唯一 ID,常用于表单 label 与 input、ARIA 属性等无障碍关联。它不是列表 key 生成器,也不应用来生成业务 ID。
详细版
组件复用时,手写固定 id 容易重复。
function Field() {
const id = useId()
return (
<>
<label htmlFor={id}>用户名</label>
<input id={id} />
</>
)
}
useId 生成的 ID 能在服务端渲染和客户端 hydration 时保持匹配,减少随机 ID 导致的结构不一致。它适合 DOM 关联,不适合 key,因为 key 应来自数据身份。
完整版教学
一、问题来自组件复用下的 id 冲突
HTML 的 id 在页面内应该唯一。
如果一个表单组件内部写死 id="name",页面复用两次就会冲突。
label 点击可能聚焦到错误 input,无障碍树也会混乱。
function BadField() {
return (
<>
<label htmlFor="name">姓名</label>
<input id="name" />
</>
)
}
如果页面有 3 个 BadField,就有 3 个相同 id。
浏览器不会替你报错,但行为可能悄悄变坏。
二、useId 生成组件实例级稳定 ID
useId 每个组件实例会得到不同 ID。
组件重新渲染时 ID 保持稳定。
这适合用来关联 label、描述文本、错误提示和 ARIA 属性。
function TextField({ label, error }) {
const id = useId()
const errorId = `${id}-error`
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} aria-describedby={error ? errorId : undefined} />
{error && <p id={errorId}>{error}</p>}
</div>
)
}
| 用途 | 是否适合 useId | 原因 |
|---|---|---|
label htmlFor | 适合 | DOM 关联 |
aria-describedby | 适合 | 无障碍关联 |
| 列表 key | 不适合 | key 应来自数据身份 |
| 数据库主键 | 不适合 | 不是业务 ID |
这里的重点是“DOM 辅助 ID”,不是“万用唯一 ID”。
三、SSR 友好是它的重要价值
如果在渲染时使用 Math.random() 生成 id,服务端和客户端生成的值可能不同。
hydration 时 React 会发现属性不一致。
useId 能根据组件树位置生成可匹配的 ID,适合 SSR。
// 不推荐
const id = Math.random().toString(36)
服务端: id=a1
客户端: id=b9
hydration 属性不一致
对于 Next.js 这类 SSR 框架,稳定 ID 很重要。 否则你可能为了一个表单 id 引入 hydration warning。
四、不要把 useId 当列表 key
列表 key 要描述数据身份。
如果一条 todo 的 id 是 42,key 就应该用 42。
useId 描述的是组件实例中的 DOM ID,不描述业务数据。
todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))
如果用 useId 给列表生成 key,你会把 React 的复用逻辑和数据身份割裂。
排序、插入、删除时可能出现不必要重建或状态错位。
这和 key 的本质相违背。
五、多个相关 ID 可以基于同一个前缀派生
一个组件可能需要 input id、hint id、error id。
不必调用很多次 useId。
可以用一次生成前缀,再拼接后缀。
const baseId = useId()
const inputId = `${baseId}-input`
const hintId = `${baseId}-hint`
const errorId = `${baseId}-error`
如果页面有 20 个表单项,每项派生 3 个 ID,一共 60 个 DOM ID。 用统一前缀能保持可读性和唯一性。
六、它服务无障碍,不只是消除警告
正确的 label 和 aria 关联能让屏幕阅读器理解表单结构。
错误提示、说明文字和控件之间需要明确关系。
useId 提供的是基础设施,真正的目标是可访问的组件。
记忆钩子:useId 给 DOM 关系发号码牌,不给数据身份发身份证。
面试回答时把 label/input 例子写出来最稳。 再强调 SSR 稳定和不能当 key。
七、常见误区与追问
- 误区:useId 可以用来生成列表 key。 key 应来自数据稳定身份,
useId用于 DOM 关联 ID。 - 误区:Math.random 生成 id 也一样。 SSR 时随机值可能导致服务端和客户端不匹配。
- 误区:useId 是业务唯一 ID。 它不应该作为数据库主键、订单号或持久化 ID。
- 追问:useId 常见场景是什么? 表单 label/input、错误提示、说明文字、ARIA 属性关联。
- 追问:多个关联元素怎么办? 用一个
useId作为前缀,拼接-hint、-error等后缀。 - 追问:为什么组件库会用它? 组件库无法要求用户手动传唯一 id,
useId能提供稳定默认值。
八、加强记忆
useId 就记两条边界:它适合生成 SSR 稳定的 DOM 关联 ID,尤其是无障碍场景;它不适合列表 key 和业务 ID。讲例子时用 label、input、aria-describedby,一下就落地了。