0. 啥是useId
Vue 3.5中新增的useId
函数主要用于生成唯一的ID,这个ID在同一个Vue应用中是唯一的,并且每次调用useId
都会生成不同的ID。这个功能在处理列表渲染、表单元素和无障碍属性时非常有用,因为它可以确保每个元素都有一个唯一的标识符。
useId
的实现原理相对简单。它通过访问Vue实例的ids
属性来生成ID,这个属性是一个数组,其中包含了用于生成ID的前缀和自增数字。每次调用useId
时,都会取出当前的数字值,然后进行自增操作。这意味着在同一页面上的多个Vue应用实例可以通过配置app.config.idPrefix
来避免ID冲突,因为每个应用实例都会维护自己的ID生成序列。
1. 实现源码
ts
export function useId(): string {
const i = getCurrentInstance()
if (i) {
return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++
} else if (__DEV__) {
warn(
`useId() is called when there is no active component ` +
`instance to be associated with.`,
)
}
return ''
}
i.appContext.config.idPrefix
:这是从当前组件实例中获取的一个配置属性,用于定义生成ID的前缀。如果这个前缀存在,它将被使用;如果不存在,默认使用'v'
。i.ids[0]
:这是当前组件实例上的ids
数组的第一个元素,它是一个字符串,通常为空字符串,用于生成ID的一部分。i.ids[1]++
:这是ids
数组的第二个元素,它是一个数字,用于生成ID的自增部分。这里使用了后置自增运算符++
,这意味着它会返回当前值然后自增。每次调用useId
时,这个数字都会增加,确保生成的ID是唯一的。
2.设置ID前缀
如果不想使用默认的前缀'v'
的话,可以通过app.config.idPrefix
进行设置。
ts
const app = createApp(App)
app.config.idPrefix = 'vid'
3.使用场景
3-1. 表单元素的唯一标识
在表单中,<label>
标签需要通过 for
属性与对应的 <input>
标签的 id
属性相匹配,以实现点击标签时输入框获得焦点的功能。使用 useId
可以为每个 <input>
元素生成一个唯一的 id
,确保这一功能的正常工作。例如:
html
<label :for="id">Do you like Vue 3.5?</label>
<input type="checkbox" :id="id" />
ts
const id = useId()
3-2. 列表渲染中的唯一键
在渲染列表时,每一项通常需要一个唯一的键(key),以帮助 Vue 追踪每个节点的身份,从而进行高效的 DOM 更新。如果你的列表数据没有唯一key的话,那么useId
可以为列表中的每个项目生成一个唯一的键。
html
<ul>
<li v-for="item in items" :key="item.id">
{{ item.text }}({{ item.id }})
</li>
</ul>
ts
const items = Array.from({ length: 10}, (v, k) => {
return {
text: `Text ${k}`,
id: useId()
}
})
上述代码渲染结果如下:
3-3. 服务端渲染(SSR)中避免 ID 冲突
在服务端渲染(SSR)的应用中,页面的HTML内容是在服务器上生成的,然后发送给客户端浏览器。在客户端,浏览器会接收到这些HTML内容,并将其转换成一个可交互的页面。如果在服务器端和客户端生成的HTML中存在相同的ID,那么在客户端激活(hydrate)时,就可能出现问题,因为客户端可能会尝试操作一个已经由服务器端渲染的DOM元素,导致潜在的冲突或错误。
下面是一个使用useId
来避免这种ID冲突的实际案例:
服务端代码 (server.js)
js
import { createSSRApp } from 'vue';
import { renderToString } from '@vue/server-renderer';
import App from './App.vue';
const app = createSSRApp(App);
// 假设我们在这里获取了一些数据
const data = fetchData();
renderToString(app).then(html => {
// 将服务端渲染的HTML发送给客户端
sendToClient(html);
});
客户端代码 (client.js)
js
import { createSSRApp } from 'vue';
import App from './App.vue';
const app = createSSRApp(App);
// 客户端激活,将服务端渲染的HTML转换成可交互的页面
hydrateApp(app);
在这个案例中,无论是服务端还是客户端,我们都使用了createSSRApp(App)
来创建应用实例。如果我们在App.vue
中使用了useId
来生成ID,那么这些ID将在服务端渲染时生成一次,并在客户端激活时再次使用相同的ID。
App.vue 组件
vue
<template>
<div>
<input :id="inputId" type="text" />
<label :for="inputId">Enter text:</label>
</div>
</template>
<script setup>
import { useId } from 'vue';
const inputId = useId();
</script>
在App.vue
组件中,我们使用了useId
来为<input>
元素生成一个唯一的ID。这个ID在服务端渲染时生成,并包含在发送给客户端的HTML中。当客户端接收到这个HTML并开始激活过程时,由于useId
生成的ID在服务端和客户端是相同的,所以客户端可以正确地将<label>
元素关联到<input>
元素,而不会出现ID冲突的问题。
如果没有使用useId
,而是使用了Math.random()
或Date.now()
来生成ID,那么服务端和客户端可能会生成不同的ID,导致客户端在激活时无法正确地将<label>
和<input>
关联起来,因为它们具有不同的ID。这可能会导致表单元素的行为异常,例如点击<label>
时,<input>
无法获得焦点。
3-4. 组件库中的 ID 生成
在使用 Element Plus 等组件库进行 SSR 开发时,为了避免 hydration 错误,需要确保服务器端和客户端生成相同的 ID。通过在 Vue 中注入 ID_injection_key
,可以确保 Element Plus 生成的 ID 在 SSR 中是唯一的。
js
// src/main.js
import { createApp } from 'vue'
import { ID_INJECTION_KEY } from 'element-plus'
import App from './App.vue'
const app = createApp(App)
app.provide(ID_INJECTION_KEY, {
prefix: 1024,
current: 0,
})
希望这篇文章介绍对你有所帮助,上述代码已托管在Gitee上,欢迎自取!