切换语言为:繁体

Vue3.5新增的useId到底有啥用?

  • 爱糖宝
  • 2024-10-25
  • 2034
  • 0
  • 0

0. 啥是useId

Vue 3.5中新增的useId函数主要用于生成唯一的ID,这个ID在同一个Vue应用中是唯一的,并且每次调用useId都会生成不同的ID。这个功能在处理列表渲染、表单元素和无障碍属性时非常有用,因为它可以确保每个元素都有一个唯一的标识符。

useId的实现原理相对简单。它通过访问Vue实例的ids属性来生成ID,这个属性是一个数组,其中包含了用于生成ID的前缀和自增数字。每次调用useId时,都会取出当前的数字值,然后进行自增操作。这意味着在同一页面上的多个Vue应用实例可以通过配置app.config.idPrefix来避免ID冲突,因为每个应用实例都会维护自己的ID生成序列。

1. 实现源码

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进行设置。

const app = createApp(App)

app.config.idPrefix = 'vid'

3.使用场景

3-1. 表单元素的唯一标识

在表单中,<label> 标签需要通过 for 属性与对应的 <input> 标签的 id 属性相匹配,以实现点击标签时输入框获得焦点的功能。使用 useId 可以为每个 <input> 元素生成一个唯一的 id,确保这一功能的正常工作。例如:

<label :for="id">Do you like Vue 3.5?</label>
  <input type="checkbox" :id="id" />

const id = useId()

3-2. 列表渲染中的唯一键

在渲染列表时,每一项通常需要一个唯一的键(key),以帮助 Vue 追踪每个节点的身份,从而进行高效的 DOM 更新。如果你的列表数据没有唯一key的话,那么useId 可以为列表中的每个项目生成一个唯一的键。

<ul>
    <li v-for="item in items" :key="item.id">
      {{ item.text }}({{ item.id }})
    </li>
  </ul>

const items = Array.from({ length: 10}, (v, k) => { 
    return {
        text: `Text ${k}`,
        id: useId()
    }
})

上述代码渲染结果如下:

Vue3.5新增的useId到底有啥用?

3-3. 服务端渲染(SSR)中避免 ID 冲突

在服务端渲染(SSR)的应用中,页面的HTML内容是在服务器上生成的,然后发送给客户端浏览器。在客户端,浏览器会接收到这些HTML内容,并将其转换成一个可交互的页面。如果在服务器端和客户端生成的HTML中存在相同的ID,那么在客户端激活(hydrate)时,就可能出现问题,因为客户端可能会尝试操作一个已经由服务器端渲染的DOM元素,导致潜在的冲突或错误。

下面是一个使用useId来避免这种ID冲突的实际案例:

服务端代码 (server.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)

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 组件

<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 中是唯一的。

// 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,
})


0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.