前言

React在很早之前的版本中加了useId,用于生成唯一ID。在Vue3.5版本中,终于也有了期待已久的useId。这篇文章来带你搞清楚useId有哪些应用场景,以及他是如何实现的。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

useId的作用

他的作用也是生成唯一ID,同一个Vue应用里面每次调用useId生成的ID都不同。

使用方法也很简单,代码如下:

<script setup lang="ts">
import { useId } from 'vue' const id0 = useId();
console.log(id0); // v-0 const id1 = useId();
console.log(id1); // v-1 const id2 = useId();
console.log(id2); // v-2
</script>

看到这里有的小伙伴会有问题,你上面的例子都是在同一组件里面调用useId。那如果我在不同的组件里面分别调用useId,这些组件生成的ID还是唯一的吗?

比如下面这个例子,父组件代码如下:

<template>
<div>
<UseIdChild1 />
<UseIdChild2 />
</div>
</template>

子组件UseIdChild1代码如下:

<script setup lang="ts">
import { useId } from "vue"; const id0 = useId();
const id1 = useId(); console.log(id0);
console.log(id1);
</script>

子组件UseIdChild2代码如下:

<script setup lang="ts">
import { useId } from "vue"; const id0 = useId();
const id1 = useId(); console.log(id0);
console.log(id1);
</script>

从上面的代码可以看到两个子组件里面的代码实际是一样的,那你猜猜子组件UseIdChild1中打印的id0id1和子组件UseIdChild2中打印的id0id1是不是一样的呢?

答案是:不一样

UseIdChild1中打印的id0的值为v-0id1的值为v-1

UseIdChild2中打印的id0的值为v-2id1的值为v-3

通过上面的这两个例子,我想你应该猜出来useId函数生成唯一ID的规律:“字符串v-加上自增的数字”。

其中的前缀v可以通过app.config.idPrefix进行自定义。

有的时候我们要渲染一个列表数据,需要列表的每一个item中有一个唯一的id,此时我们就可以使用useId去给每个item生成唯一的id。

这个是最简单的使用场景,接下来我们看看在服务端渲染(SSR)中useId的使用场景。

在服务端渲染(SSR)中使用useId

首先我们要搞清楚服务端渲染时有哪些痛点?

我们来看一个服务端渲染的例子,代码如下:

<template>
<div>
<label :htmlFor="id">Do you like Vue3.5?</label>
<input type="checkbox" name="vue3.5" :id="id" />
</div>
</template> <script setup lang="ts">
const id = Math.random();
</script>

上面的代码如果是跑在客户端渲染时没有任何问题,但是如果在服务端渲染时就会有警告了。如下图:

上面的警告意思是,在服务端时生成的id的值为0.4050816845323888。但是在客户端时生成的id的值却是0.4746900241123273,这两次生成的id值不同,所以才会出现警告。

可能有的小伙伴会有疑问,为什么在服务端生成一次id后,在客户端又去生成一次id呢?

为了解答上面这个问题,我们先来了解一下服务端渲染(SSR)的流程:

  • 首先会在服务端(Node.js环境)发起接口请求,从后端拿到页面渲染需要的数据。

  • 根据拿到的数据去生成页面的HTML字符串,此时就会在服务端生成一次id,这一步叫dehydrate(脱水)。

  • 将服务端生成的HTML字符串发送给客户端(浏览器)。

  • 浏览器拿到了服务端生成的HTML字符串可以将其作为首屏内容,直接渲染到页面上。但是此时click之类的事件还没绑定在DOM上,所以在客户端需要再渲染一次。就会在客户端再次生成一次id,这一步叫hydrate(注水)。

由于我们这里是使用Math.random()去生成的id,在服务端和客户端每次执行Math.random()生成的id值当然就不同了,所以才会出现上面的警告。

有了useId后,解决上面的警告就很简单了,只需要把Math.random()改成useId()就可以了。代码如下:

<template>
<div>
<label :htmlFor="id">Do you like Vue3.5?</label>
<input type="checkbox" name="vue3.5" :id="id" />
</div>
</template> <script setup lang="ts">
const id = useId();
</script>

因为useId在服务端渲染时会生成v-0,在客户端渲染时依然还是v-0

可能有的小伙伴有疑问,前面不是讲的useId每执行一次会给后面的数字+1。那么服务端执行一次后,再去客户端执行一次,讲道理应该生成的ID不一样吧??

useId生成的“自增数字部分”是维护在vue实例上面的ids属性上,服务端渲染时会在Node.js端生成一个vue实例。但是客户端渲染时又会在浏览器中重新生成一个新的vue实例,此时vue实例上的ids属性也会被重置,所以在服务端和客户端执行useId生成的值是一样的。

useId是如何实现的

我们来看看useId的源码,非常简单!!简化后的代码如下:

function useId(): string {
const i = getCurrentInstance()
if (i) {
return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++
}
return ''
}

这个getCurrentInstance函数我想很多同学都比较熟悉,他的作用是返回当前vue实例。

useId打个断点,来看一下当前vue实例i,如下图:

从上图中可以看到vue实例上的ids属性是一个数组,数组的第一项是空字符串,第二项是数字0,第三项也是数字0

我们再来看看useId是如何返回唯一ID的,如下:

return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++

生成的唯一ID由三部分组成:

  • 第一部分为前缀,从app.config.idPrefix中取的。如果没有配置,那么就是字符串v

  • 第二部分为写死的字符串-

  • 第三部分为i.ids[0] + i.ids[1]++,其中ids[0] 的值为空字符串。i.ids[1]++这里是先取值,然后再执行++,所以第三部分的值为数字0。再次调用useId时,由于上一次执行过一次++了。此时的数字值为1,并且再次执行++

看到这里有的小伙伴又有疑问了,这里看上去ids属性是存在vue实例上面的。每个vue组件都有一个vue实例,那么每个组件都有各自维护的ids属性。

那你前面的那个例子中UseIdChild1子组件和UseIdChild2子组件中各自生成的id0的值应该是一样的v-0吧,为什么一个是v-0,另外一个是v-2呢?

答案其实很简单,所有vue实例上面的ids属性都是同一个数组,指向的是顶层组件实例上面的那个ids属性。创建vue实例的源码如下图:

从上图中可以看到当没有父组件时,也就是最顶层的vue组件实例,就将其ids属性设置为数组['', 0, 0]

当生成子组件的vue实例时,由于父组件上面有ids属性,所以就用父组件上面的了。指针都是指向的是最顶层vue实例上面的ids属性,所以才会说所有的vue组件实例上面的ids属性都是指向同一个数组。

这也就是为什么UseIdChild1子组件和UseIdChild2子组件中各自生成的id0的值一个是v-0,另外一个是v-2

总结

Vue3.5新增的useId可以在Vue应用内生成唯一的ID,我们可以使用useId给列表数据中的每一个item生成一个唯一的id。

并且在服务端渲染(SSR)场景中,服务端和客户端执行useId生成的是同一个ID。利用这个特点我们可以使用useId解决一些在 SSR 应用中,服务器端和客户端生成的 ID 不一致导致的警告。

最后我们讲了useId的实现也很简单,生成的ID分为三部分:

  • 第一部分为前缀:app.config.idPrefix,如果没有配置,那么就是字符串v

  • 第二部分字符串:-

  • 第三部分的值为一个自增的数字,存在vue实例上面的ids属性,所有的vue实例上面的ids属性都是指向同一个数组。这也就是为什么说useId可以在Vue应用内生成唯一的ID,而不是在Vue组件内生成唯一的ID。

关注公众号:【前端欧阳】,给自己一个进阶vue的机会

另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。

React的useId,现在Vue3.5终于也有了!的更多相关文章

  1. 从省市区多重级联想到的,react和jquery的差别

    在我们的前端项目里经常会用到级联的select,比如省市区这样.通常这种级联大多是动态的.比如先加载了省,点击省加载市,点击市加载区.然后数据通常ajax返回.如果没有数据则说明到了叶子节点.   针 ...

  2. 深入 Create React App 核心概念

    本文差点难产而死.因为总结的过程中,多次怀疑本文是对官方文档的直接翻译和简单诺列:同时官方文档很全面,全范围的介绍无疑加深了写作的心智负担.但在最终的梳理中,发现走出了一条与众不同的路,于是坚持分享出 ...

  3. 【独家】React Native 版本升级指南

    前言 React Native 作为一款跨端框架,有一个最让人头疼的问题,那就是版本更新.尤其是遇到大版本更新,JavaScript.iOS 和 Android 三端的配置构建文件都有非常大的变动,有 ...

  4. 前端框架大比拼:2022年的Vue与React谁更胜一筹?

    携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 18 天,点击查看活动详情 前端框架经历了十多年的争奇斗艳百花齐放,经历了 JSP.jQuery.Ember.Angular.R ...

  5. 删除github仓库中的某个文件夹

    最近在做一个项目,由于前期文件夹名是中文,如下:    |---Repository       |--- React单页面音乐播放器 并且git push到了github上. 后来在本地把文件夹re ...

  6. 【前端vue进阶实战】:从零打造一个流程图、拓扑图项目【Nuxt.js + Element + Vuex】 (一)

    本系列教程是用Vue.js + Nuxt.js + Element + Vuex + 开源js绘图库,打造一个属于自己的在线绘图软件,最终效果:topology.le5le.com .如果你觉得好,欢 ...

  7. [译]ABP框架v2.3.0已经发布!

    在新冠病毒的日子里,我们发布了ABP框架v2.3, 这篇文章将说明本次发布新增内容和过去的两周我们做了什么. 关于新冠病毒和我们的团队 关于冠状病毒的状况我们很难过.在Volosoft的团队,我们有不 ...

  8. Redux/Mobx/Akita/Vuex对比 - 选择更适合低代码场景的状态管理方案

    近期准备开发一个数据分析 SDK,定位是作为数据中台向外输出数据分析能力的载体,前端的功能表现类似低代码平台的各种拖拉拽.作为中台能力的载体,SDK 未来很大概率会需要支持多种视图层框架,比如Vue2 ...

  9. vue3的学习笔记:MVC、Vue3概要、模板、数据绑定、用Vue3 + element ui、react框架实现购物车案例

    一.前端MVC概要 1.1.库与框架的区别 框架是一个软件的半成品,在全局范围内给了大的约束.库是工具,在单点上给我们提供功能.框架是依赖库的.Vue是框架而jQuery则是库. 1.2.MVC(Mo ...

  10. 一个基于 Vue3 的开源项目,3个月时间 star 终于破千!

    本文主要是对如何做开源项目的一些思考. 前文回顾: <Vue3 来了,Vue3 开源商城项目重构计划正式启动!> <一个基于 Vue 3 + Vant 3 的开源商城项目> 关 ...

随机推荐

  1. AT_agc017_b 题解

    洛谷链接&Atcoder 链接 本篇题解为此题较简单做法,请放心阅读. 题目简述 一共有 \(n\) 个格子,给定两个整数 \(A,B\) 分别位于第 \(1\) 和第 \(n\) 格,中间有 ...

  2. redis环境的安装

    Redis环境的安装(源码安装),主要分为单机安装与集群安装,无论是单机安装还是集群安装,Redis本身的依赖是必须要有的,本文所采用的Redis版本是redis-5.0.3,所需要的依赖如下: cp ...

  3. 【Vue】04 模块化开发演变

    JS最初的目的是用来做表单验证和动画效果,可以让网页更加生动. 但是使用Ajax,前后端分离,页面承担了更多的事情,JS的代码量暴增,代码管理维护逐渐困难 我们需要将JS代码抽取出来,模块化处理, 但 ...

  4. 【转载】PPT上作图转化为矢量图 —— 论文写作必备技能

    原文地址: https://www.cnblogs.com/scandit/p/15112138.html PPT作出的图本身就为矢量图(可以插入到Word中),另存为PDF后,再通过PDF专业工具适 ...

  5. 首次配置成功rllab运行环境,给出anaconda下的配置

    name: rllab channels: - defaults dependencies: - _libgcc_mutex=0.1=main - _openmp_mutex=5.1=1_gnu - ...

  6. vscode中文环境配置

    1.背景 2.配置 2.1.安装中文包 如果没有按照中文插件需要先按照中文插件 如果你是首次安装,安装完成后会引导你重启,就可以了 2.2.设置成中文环境 打开VSCode软件,按快捷键[Ctrl+S ...

  7. cdq分治 基础篇

    简介 前置芝士:归并排序. \(cdq\) 分治是个离线算法,可以解决三维偏序或者优化 \(dp\). 陌上花开 维护三维偏序有个口诀:一维排序,二维归并,三维数据结构. 考虑第一维直接排序解决掉,然 ...

  8. 9组-Alpha冲刺-6/6

    一.基本情况 队名:不行就摆了吧 组长博客: https://www.cnblogs.com/Microsoft-hc/p/15546711.html 小组人数: 8 二.冲刺概况汇报 张伟鹏 过去两 ...

  9. 07-canvas绘制虚线

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  10. Camera | 4.瑞芯微平台MIPI摄像头应用程序编写

    前面3篇我们讲解了camera的基础概念,MIPI协议,CSI2,常用命令等,本文带领大家入门,如何用c语言编写应用程序来操作摄像头. Linux下摄像头驱动都是基于v4l2架构,要基于该架构编写摄像 ...