或者用yarn
Vue3开发中怎么生成唯一标识?UUID怎么选、怎么用、踩过的坑全说透
做Vue3项目的时候,大家肯定遇到过需要给列表项、组件、表单提交的临时数据加唯一标识的场景吧?最开始我也只会自己拼个“时间戳+随机数”,结果偶尔还是会撞——比如循环渲染组件的时候手速快,生成的随机数没跟上时间戳的精度,两个组件就拿到了一样的key,控制台直接报“Duplicate keys detected”的警告,搞不好还会导致组件复用出错,后来慢慢接触到UUID,才发现这才是生成稳定唯一值的专业工具,不过网上的教程要么太老套,要么只讲怎么安装,没提Vue3的组合式API适配,更没讲各种UUID版本的区别和实际踩的坑,今天就把我这两年摸Vue3和UUID的经验都整理出来,不管是新手入门还是进阶避坑,应该都能用上。
UUID是什么?它和我们自己拼的“时间戳+随机数”有啥不一样?
先搞清楚最基础的东西,不然用起来心里没底,UUID的全称是通用唯一识别码,简单说就是一套能在任意时间、任意地点,生成几乎不可能重复的字符串的规则,注意哦,是“几乎不可能”,数学上理论上还是有概率的,但这个概率低到什么程度呢?有数据显示,就算每秒钟生成10亿个UUID,连续生成100年,重复的概率才只有万亿分之一,相当于你随便选一粒宇宙中的特定氢原子,第二次还能选中同一粒的概率,完全可以忽略不计。
那自己拼的那种“Date.now() + Math.random()”为什么容易撞?两个核心问题:一是精度不够,浏览器里Date.now()的精度通常是毫秒级,如果你在同一个毫秒内多次调用这个函数,时间戳部分就完全一样,剩下的Math.random()虽然是伪随机,但它的生成范围和周期有限,在短时间内重复的概率会直线上升;二是没有标准化,不同开发者拼的规则不一样,长度也不固定,存到数据库里可能会因为字段长度不够截断,反而更容易撞,而且也没法和其他系统对接——其他系统用的都是标准UUID,你发过去一串自己拼的,人家根本识别不了你的唯一性。
Vue3开发中,UUID选哪个版本最常用?有没有更轻量的替代?
很多人可能不知道,UUID其实有好几个版本,从v1到v5都有,每个版本的生成规则都不一样,适用场景也完全不同,我之前刚接触的时候,直接搜了“Vue3 UUID”就装了个最大众的包,结果生成的UUID包含了MAC地址,差点在项目上线前被安全组打回来重做,所以先把版本搞清楚是第一步。
常见的UUID版本对比
先给大家列个核心的版本清单,不用记太复杂的生成原理,记住每个版本的特点和什么时候用就行:
- UUID v1:基于时间戳+MAC地址生成,这个版本的特点是生成速度快,而且能保证时间上的有序性(因为前几位是时间戳),但缺点非常致命——会暴露设备的MAC地址,虽然浏览器环境里因为安全限制,MAC地址是伪造的,但服务器端如果用Node.js生成v1 UUID的话,可能会暴露服务器的真实MAC,绝对不能用在对外暴露的接口里,也不能存到需要公开的数据字段里。
- UUID v4:完全基于随机数生成,这个版本是目前Vue3甚至整个前端开发中最常用的,没有之一,为什么?因为它不包含任何敏感信息,生成的字符串完全随机,重复概率又极低,而且不管是浏览器还是Node.js环境都能用,适用场景最广——列表key、组件临时ID、表单提交前的临时记录标识、WebSocket消息的唯一标识,这些场景用v4都没问题。
- UUID v3和v5:基于命名空间+名称生成,这两个版本的特点是确定性——也就是同样的命名空间+同样的名称,生成的UUID永远是一样的,适合用来做哈希映射,比如把用户的手机号(经过脱敏处理)转换成唯一的用户标识,或者把文件的路径转换成唯一的文件ID,v3用的是MD5哈希算法,v5用的是SHA-1,现在SHA-1虽然也被破解了一些,但安全性比MD5还是高一点,所以如果要用确定性UUID,优先选v5。
- UUID v2:这个版本很少见,是基于时间戳+MAC地址+本地标识符(比如用户ID)生成的,主要用于DCE(分布式计算环境),前端和Node.js开发几乎用不到,知道有这么个版本就行。
有没有比标准UUID更轻量的替代?
标准的UUID v4长度是36位(550e8400-e29b-41d4-a716-446655440000”),如果你的项目对性能或者存储有极致的要求,比如做移动端H5或者小程序,列表可能要渲染几千上万条数据,每个key都占36位的话,虽然理论上对性能的影响微乎其微,但总有人想抠那点细节,这时候可以用nanoid,它是现在非常流行的UUID轻量替代方案,核心特点有三个:
- 更短:默认生成21位字符,比UUID短了40%左右,但重复概率和UUID v4一样低——因为它用的是更大的字符集(64个字符:大小写字母、数字、下划线、短横线),不是UUID的16个字符。
- 更快:生成速度比标准UUID v4快好几倍,尤其是在浏览器环境里。
- 更安全:默认使用浏览器的Crypto API(如果有的话)生成真随机数,Node.js环境用的是crypto模块,不是Math.random()这种伪随机。 nanoid的用法和UUID v4几乎一模一样,而且也有专门的Vue3适配吗?其实不用,它本身就是一个纯JavaScript库,组合式API里直接引入就能用,后面会给大家讲具体的代码。
Vue3中怎么安装和使用UUID/nanoid?组合式API怎么封装?
讲完了选哪个,接下来就是具体的操作了,我会分两种情况讲:一种是用官方的UUID npm包,另一种是用更轻量的nanoid,两种都会讲基础用法和Vue3组合式API的封装,方便大家在项目里复用。
先讲最基础的:安装和引入
不管用UUID还是nanoid,第一步都是安装npm包,现在的Vue3项目基本都是用Vite或者Vue CLI创建的,直接在终端里敲命令就行。
安装UUID官方包
UUID的官方npm包就叫uuid,包名非常好记,安装命令:
npm install uuidyarn add uuid # 或者用pnpm(推荐,更快更省空间) pnpm add uuid
安装完成后,怎么引入呢?如果是Vue3的组合式API(也就是<script setup>语法),可以直接按需引入需要的版本函数,不用引入整个包,这样能减少最终打包后的体积,比如只引入最常用的v4版本:
import { v4 as uuidv4 } from 'uuid';
注意哦,这里的as uuidv4是重命名,因为原来的函数名就叫v4,直接写v4()有点太简洁了,代码可读性不好,重命名成uuidv4之后,别人看你的代码就知道你生成的是UUID v4版本。
安装nanoid
nanoid的npm包也叫nanoid,安装命令:
npm install nanoid # 或者yarn/pnpm yarn add nanoid pnpm add nanoid
引入的话更简单,因为它默认导出的就是生成函数,也是按需引入吗?其实nanoid本身就非常小,只有几百字节,按需引入的意义不大,直接默认引入就行:
import { nanoid } from 'nanoid';
如果你想自己指定生成的长度,比如生成10位的字符,也是可以的,后面会讲具体的参数。
基础用法:直接生成唯一值
不管是UUID v4还是nanoid,基础用法都非常简单,直接调用函数就行。
UUID v4的基础用法
直接调用uuidv4()函数,就会返回一个标准的36位UUID v4字符串:
// 生成一个UUID v4 const id = uuidv4(); console.log(id); // 比如输出:550e8400-e29b-41d4-a716-446655440000
nanoid的基础用法
直接调用nanoid()函数,返回一个默认21位的字符串:
// 生成一个默认21位的nanoid const id = nanoid(); console.log(id); // 比如输出:V1StGXR8_Z5jdHi6B-myT
如果想自己指定长度,比如生成10位的,直接传个数字参数就行:
// 生成一个10位的nanoid const shortId = nanoid(10); console.log(shortId); // 比如输出:fP9mhKqLxZ
不过要注意,长度越短,重复概率就越高,21位的重复概率已经足够低了,没必要为了更短而冒风险,除非你的项目有特殊要求。
进阶用法:Vue3组合式API封装,方便全项目复用
如果你的项目里很多地方都要用到唯一标识,每次都要引入uuidv4或者nanoid,太麻烦了,而且如果以后想换一种生成方式(比如从UUID v4换成nanoid),还要改所有的引入和调用代码,维护成本很高,这时候可以用Vue3的组合式API,把生成唯一值的逻辑封装成一个自定义Hook,放在src/composables目录下(如果没有这个目录,自己创建一个就行,这是Vue3官方推荐的自定义Hook存放位置),这样全项目都能复用,想换生成方式的话,只需要改这个Hook就行。
封装UUID v4的自定义Hook
先创建一个文件,比如叫src/composables/useUUID.js:
import { v4 as uuidv4, v5 as uuidv5 } from 'uuid';
// 定义一个命名空间,用于UUID v3和v5的确定性生成
// 这个命名空间可以自己随便生成一个UUID v4,然后固定下来就行
const NAMESPACE = '550e8400-e29b-41d4-a716-446655440000';
export function useUUID() {
// 生成UUID v4
const generateUUIDv4 = () => {
return uuidv4();
};
// 生成UUID v5(确定性)
const generateUUIDv5 = (name) => {
// name必须是字符串,如果不是的话,先转换成字符串
if (typeof name !== 'string') {
name = JSON.stringify(name);
}
return uuidv5(name, NAMESPACE);
};
return {
generateUUIDv4,
generateUUIDv5
};
}
封装好之后,在任何组件的<script setup>里都能直接用:
<template>
<div>
<h3>UUID v4演示</h3>
<p>点击按钮生成新的UUID v4:</p>
<button @click="newUUIDv4">生成UUID</button>
<p>当前UUID:{{ currentUUIDv4 }}</p>
<h3>UUID v5演示</h3>
<p>输入名称生成确定性UUID v5:</p>
<input type="text" v-model="inputName" placeholder="请输入名称" />
<button @click="newUUIDv5">生成UUID</button>
<p>当前UUID:{{ currentUUIDv5 }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useUUID } from '@/composables/useUUID';
// 解构自定义Hook
const { generateUUIDv4, generateUUIDv5 } = useUUID();
// UUID v4的响应式变量
const currentUUIDv4 = ref('');
const newUUIDv4 = () => {
currentUUIDv4.value = generateUUIDv4();
};
// UUID v5的响应式变量
const inputName = ref('');
const currentUUIDv5 = ref('');
const newUUIDv5 = () => {
if (!inputName.value.trim()) {
alert('请输入名称');
return;
}
currentUUIDv5.value = generateUUIDv5(inputName.value.trim());
};
</script>
这个组件的效果很简单:点击第一个按钮会生成一个随机的UUID v4;输入一个名称,点击第二个按钮会生成一个和这个名称绑定的UUID v5,不管你输入多少次同样的名称,生成的UUID都是一样的,大家可以自己复制这段代码试试。
封装nanoid的自定义Hook
封装nanoid的Hook和封装UUID的差不多,甚至更简单,因为nanoid主要就是用来生成随机值的,很少用来做确定性生成(虽然它也有确定性生成的插件,但一般不需要),创建文件src/composables/useNanoid.js:
import { nanoid } from 'nanoid';
export function useNanoid() {
// 生成默认21位的nanoid
const generateNanoid = (length = 21) => {
// 做个参数校验,防止传入非数字或者小于1的数字
if (typeof length !== 'number' || length < 1) {
length = 21;
}
return nanoid(length);
};
return {
generateNanoid
};
}
使用的时候也一样:
<template>
<div>
<h3>nanoid演示</h3>
<p>点击按钮生成新的nanoid:</p>
<button @click="newNanoid(21)">生成21位(默认)</button>
<button @click="newNanoid(10)">生成10位</button>
<button @click="newNanoid(36)">生成36位(和UUID一样长)</button>
<p>当前nanoid:{{ currentNanoid }}</p>
<p>当前长度:{{ currentNanoid.length }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useNanoid } from '@/composables/useNanoid';
const { generateNanoid } = useNanoid();
const currentNanoid = ref('');
const newNanoid = (length) => {
currentNanoid.value = generateNanoid(length);
};
</script>
同样,大家可以复制这段代码试试,生成36位的nanoid和UUID v4长度一样,但字符集更大,看起来更“乱”,但重复概率更低。
Vue3中用UUID/nanoid踩过的坑有哪些?怎么避免?
讲完了怎么用,接下来就是最重要的避坑环节了,我自己在项目里踩过不少坑,也见过身边的同事踩过,今天把最常见的几个列出来,大家一定要注意。
坑1:UUID v1暴露服务器MAC地址
这个坑我前面提过,差点在项目上线前被安全组打回来,当时我在Node.js的后端接口里用了UUID v1生成订单ID,结果安全组扫描接口返回的数据的时候,发现订单ID里包含了服务器的真实MAC地址,要求我赶紧换掉,不然项目不能上线,后来我换成了UUID v4,问题就解决了。
怎么避免:不管是前端还是后端,只要是对外暴露的数据,绝对不要用UUID v1,优先用UUID v4或者nanoid。
坑2:在<script setup>的顶层直接调用生成函数,导致每次刷新页面或者组件重新挂载的时候,ID都不变
这个坑是新手最容易踩的,比如你想给一个组件加一个唯一的临时ID,用来绑定DOM元素或者做其他操作,于是你直接在<script setup>的顶层写了const componentId = uuidv4();,结果你会发现,每次刷新页面或者组件重新挂载的时候,componentId都是一样的?不对,等一下,是不是每次都不一样?哦,不对,有时候确实会一样?不,等一下,我再想想——哦,不对,新手踩的这个坑其实反过来:有时候他们会把生成的ID存在localStorage里,然后在顶层直接读取,导致每次都一样,但如果没有存localStorage的话,顶层直接调用uuidv4()应该每次刷新页面都会不一样啊?哦,不对,我记错了,另一个更常见的坑是:在循环渲染组件的时候,把生成的ID直接写在模板的表达式里,比如key="uuidv4()",这就更惨了——每次组件重新渲染(比如数据变化、父组件传递的props变化),循环的每个key都会重新生成,Vue3就会认为所有的组件都是新的,会把旧的组件全部销毁,然后重新创建新的组件,这不仅会导致严重的性能问题,还会丢失组件的状态(比如表单输入框里的内容会被清空)。
对,这个才是最常见的坑!我之前有个同事就犯过这个错误,循环渲染了一个有十几个表单输入框的列表,结果每次用户输入一个框里的内容,列表就会重新渲染,所有的框都清空了,他找了好久才找到原因。
怎么避免:
-
循环渲染的时候,绝对不要在模板的
key里直接调用生成函数,一定要在数据初始化的时候就给每个元素加上唯一的ID,然后把这个ID作为key。<template> <ul> <!-- 错误的写法:每次渲染都会生成新的key --> <!-- <li v-for="item in list" :key="uuidv4()">{{ item.name }}</li> --> <!-- 正确的写法:用数据本身的唯一ID作为key --> <li v-for="item in list" :key="item.id">{{ item.name }}</li> </ul> </template> <script setup> import { ref } from 'vue'; import { useUUID } from '@/composables/useUUID'; const { generateUUIDv4 } = useUUID(); // 初始化列表的时候就给每个元素加上ID const list = ref([ { id: generateUUIDv4(), name: '张三' }, { id: generateUUIDv4(), name: '李四' }, { id: generateUUIDv4(), name: '王五' } ]); // 添加新元素的时候也给新元素加上ID const addItem = (name) => { list.value.push({ id: generateUUIDv4(), name: name }); }; </script> -
如果要给组件加一个唯一的临时ID,用来绑定DOM元素,应该在
onMounted钩子或者ref的初始值里调用生成函数,但要注意,ref的初始值只会在组件第一次挂载的时候调用一次,组件重新挂载的时候会再次调用,所以如果需要ID在组件多次挂载之间保持不变,应该把ID存在localStorage或者sessionStorage里。
坑3:在SSR(服务端渲染)项目中直接使用浏览器的Crypto API
如果你的Vue3项目用的是SSR(比如Nuxt.js 3),那就要注意了:nanoid默认会优先使用浏览器的window.crypto.getRandomValues()生成真随机数,但在服务端(Node.js)环境里,是没有window对象的,虽然nanoid官方包已经做了适配,会自动切换到Node.js的crypto模块,但如果你用的是旧版本的nanoid,或者自己手动用了浏览器的Crypto API,就会在服务端渲染的时候报错。
怎么避免:
- 尽量用最新版本的nanoid或者UUID官方包,它们都已经做好了SSR适配。
- 如果一定要自己手动生成随机数,可以用Vue3的
process.client或者Nuxt.js的useClientOnly钩子,确保只在客户端执行生成随机数的代码。
坑4:UUID的大小写问题导致唯一性失效
虽然标准的UUID是不区分大小写的(550e8400-e29b-41d4-a716-446655440000”和“550E8400-E29B-41D4-A716-446655440000”是同一个UUID),但有些数据库或者后端接口是区分大小写的,如果你在前端生成的是小写的UUID,存到数据库里变成了大写的,或者反过来,就会导致唯一性失效,比如查询的时候查不到对应的记录,或者插入的时候报错“重复键”。
怎么避免:
- 前后端约定好UUID的大小写格式,比如统一用小写,或者统一用大写。
- 在前端生成UUID之后,手动转换成统一的格式,
const id = uuidv4().toLowerCase(); // 统一转换成小写
坑5:UUID的长度超过数据库字段的限制
这个坑虽然不常见,但如果你的数据库字段设置得太短,就会遇到,比如有些开发者为了省空间,把存储UUID的字段设置成了VARCHAR(32),但标准的UUID v4是36位(包含4个短横线),存进去的时候就会被截断,变成32位,重复概率就会大幅上升。
怎么避免:
- 如果用标准的UUID v4,数据库字段至少要设置成VARCHAR(36),或者直接用数据库自带的UUID类型(比如MySQL的CHAR(36),PostgreSQL的UUID),用数据库自带的UUID类型不仅更省空间,而且查询速度更快。
- 如果用nanoid,可以根据自己生成的长度设置数据库字段,比如默认21位的话,设置成VARCHAR(21)就行。
Vue3开发中唯一标识的最佳实践
讲了这么多,最后给大家总结一下Vue3开发中唯一标识的最佳实践,方便大家快速上手:
- 优先选什么:如果没有特殊要求,优先用nanoid,因为它更轻量、更快、更安全;如果需要和其他系统对接,或者需要用标准的UUID格式,优先用UUID v4;如果需要确定性生成,用UUID v5。
- 怎么封装:把生成唯一值的逻辑封装成Vue3的自定义Hook,放在
src/composables目录下,方便全项目复用,维护成本更低。 - 怎么避坑:绝对不要在循环渲染的
key里直接调用生成函数;对外暴露的数据不要用UUID v1;SSR项目要用最新版本的包,或者确保只在客户端执行;前后端约定好大小写格式;数据库字段要足够长,或者用数据库自带的UUID类型。
就是我这两年摸Vue3和UUID的所有经验了,希望能帮到大家,如果大家还有其他问题,或者有更好的实践,欢迎在评论区留言交流。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


