简介
useId是新增的用于生成唯一ID值的hook钩子,主要用于客户端和服务器端使用,同时避免 dehydrate 过程中数据不匹配的问题。
它主要用于与需要唯一 ID 的可访问性 API 集成的组件库。这解决了 React 17 及更低版本中已经存在的问题,但在 React 18 中更为重要,因为新的流式服务器渲染器如何无序交付 HTML。
但是,不建议用于List中作为key使用,列表中的唯一key应该使用List中的数据。
问题
React渲染有客户端渲染(CSR)和服务端渲染(SSR)。
假设有如下代码片段:
// App.tsx
const id = Math.random();
export default function App() {
return Hello
}
如果应用是CSR(客户端渲染),id是稳定的,App组件没有问题。
但如果应用是SSR(服务端渲染),那么App.tsx会分为以下几步:
- React在服务端渲染,生成随机id(假设为0.1234),这一步叫dehydrate(脱水);
- Hello作为HTML传递给客户端,作为首屏内容;
- React在客户端渲染,生成随机id(假设为0.6789),这一步叫hydrate(注水)。
原始解决方式:
// 全局通用的计数变量
let globalIdIndex = 0;
export default function App() {
const id = useState(() => globalIdIndex++);
return Hello
}
只要React在服务端、客户端的运行流程一致,那么双端产生的id就是对应的。
但是,随着React Fizz(React新的服务端流式渲染器)的到来,渲染顺序不再一定。
比如,有个特性叫 Selective Hydration,可以根据用户交互改变hydrate的顺序。
当下图左侧部分在hydrate时,用户点击了右下角部分:
文章图片
此时React会优先对右下角部分hydrate:
文章图片
因此,自增的全局计数变量作为id,不再准确!!
那么,有没有什么是服务端、客户端都稳定的标记呢?
答案是:组件的层次结构。
useId的原理 假设应用的组件树如下图:
文章图片
不管B和C谁先hydrate,他们的层级结构是不变的,所以「层级」本身就能作为服务端、客户端之间不变的标识。
比如B可以使用2-1作为id,C使用2-2作为id:
function B() {
// id为"2-1"
const id = useId();
return B;
}
【React中的useId】如何在一个组件中使用多个useId()?
react推荐使用相同的id+后缀:
function NameFields() {
const id = useId();
return ();
}