当前位置: 首页 > news >正文

前端如何优雅地生成唯一标识?——一份跨环境 UUID 工具函数的封装与实战

关键词:UUID、crypto.randomUUID、polyfill、兼容性、Node、浏览器、TypeScript

一、背景:为什么不要直接 Math.random()

前端经常需要“全局唯一” id,常见场景:

场景是否可重复后果
上传文件临时名秒传失效 / 覆盖
表单多条动态行删除错位
日志 / 埋点 traceId链路串线

Math.random() 随机熵只有 48 bit,理论碰撞概率 1/2²⁴≈1/16M,在 toB 长生命周期系统里并不安全。
Web 标准早已给出加密级方案:crypto.randomUUID(),但存在三把“隐形锁”:

  1. 仅 HTTPS / localhost(Secure Context)
  2. 2022 年才普及,IE、Chrome<92 直接抛错
  3. Node 需要 ≥14.17,且需 import { randomUUID } from 'node:crypto'

本文目标:一份代码,同时跑在

  • 现代浏览器(优先原生)
  • 旧浏览器(自动降级)
  • Node / SSR(Jest、Next.js、Electron 主进程)
  • 小程序(微信 / 支付宝,提供 polyfill 入口)

二、设计思路:能力探测 + 分层降级

┌─ ① 原生 crypto.randomUUID (最优)
├─ ② crypto.getRandomValues 手动 v4
├─ ③ Node crypto.randomUUID (SSR)
└─ ④ 最末兜底 Math.random(警告)

策略:

  • 运行期探测,不污染全局
  • Tree-shakable,无三方依赖,包体 <1 KB
  • TypeScript 全类型,支持 ESM / CJS 双格式
  • 可Mock,测试友好

三、源码:uuid.ts(零依赖)

/* eslint-disable no-console */
type UUID = `${string}-${string}-${string}-${string}-${string}`;const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;/** 判断当前是否 Secure Context,可解锁 crypto.randomUUID */
const isSecureContext = () =>typeof globalThis.crypto !== 'undefined' &&typeof crypto.randomUUID === 'function';/** 判断 Node 环境 */
const isNode = () =>typeof process !== 'undefined' &&process.versions?.node;/** 使用 crypto.getRandomValues 手动拼 v4 UUID */
const uuidv4ByRandomValues = (): UUID => {const bytes = new Uint8Array(16);crypto.getRandomValues(bytes);// Per RFC4122, set version (4) and variant (1)bytes[6] = (bytes[6]! & 0x0f) | 0x40;bytes[8] = (bytes[8]! & 0x3f) | 0x80;const hex = [...bytes].map(b => b.toString(16).padStart(2, '0'));return `${hex.slice(0, 4).join('')}-${hex[4]}${hex[5]}-${hex[6]}${hex[7]}-${hex[8]}${hex[9]}-${hex.slice(10, 16).join('')}` as UUID;
};/** Node 原生 */
const uuidv4ByNode = (): UUID => {// eslint-disable-next-line @typescript-eslint/no-var-requiresconst { randomUUID } = require('node:crypto') as { randomUUID: () => UUID };return randomUUID();
};/** 最末兜底:控制台告警,生产请避免 */
const uuidv4ByMath = (): UUID => {console.warn('[uuid] falling back to Math.random() — NOT crypto secure!');return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {const r = (Math.random() * 16) | 0;const v = c === 'x' ? r : (r & 0x3) | 0x8;return v.toString(16);}) as UUID;
};/*** 跨环境 UUID v4 生成器* @returns RFC4122 标准 UUID*/
export function uuid(): UUID {if (isSecureContext()) return crypto.randomUUID() as UUID;if (typeof crypto !== 'undefined' && crypto.getRandomValues) {return uuidv4ByRandomValues();}if (isNode()) return uuidv4ByNode();return uuidv4ByMath();
}/** 校验字符串是否为合法 UUID */
export const isUUID = (str: string): str is UUID => UUID_REGEX.test(str);

四、使用示例

1. ESM(Vite / Webpack)

import { uuid } from '@/utils/uuid';console.log(uuid()); // a1b2c3d4-e5f6-4g8h-ij90-klmnopqrstuv

2. CommonJS(Node 脚本)

const { uuid } = require('./dist/uuid.cjs');
console.log(uuid());

3. 小程序入口 polyfill(微信)

微信没有 crypto,在 app.ts 顶部一次性注入:

import 'polyfill-crypto-methods'; // npm i polyfill-crypto-methods
// 现在全局有 crypto.randomUUID / getRandomValues

4. Jest 测试固定值

jest.mock('@/utils/uuid', () => ({ uuid: () => 'test-uuid-123' }));

五、性能 & 体积对比

方案包体 (gzip)生成 10w 次 (Chrome 119)备注
本文函数0.3 KB85 ms原生最快
uuid@9 库4.2 KB95 ms稳定,需安装
nanoid@51.1 KB65 ms非标准 UUID 格式

测试机:M1 Mac / Node 20
结论:现代浏览器直接用原生,包体≈0;旧端自动降级,无需业务感知。

六、踩坑记录

坑位现象解决
crypto.randomUUID 在 http 线下 502报错 TypeError: crypto.randomUUID is not a function先探测 isSecureContext,再降级
Node 14 找不到 randomUUID同上报错require('node:crypto') 动态导入
小程序 undefined没有全局 crypto引入 polyfill-crypto-methods
Webpack 4 把 process 打进包导致 isNode() 误判配置 node: { process: false }

七、FAQ

Q1. 为什么不用 Date.now() + Math.random()
→ 时间戳可被预测,且碰撞概率高,不符合 RFC4122。

Q2. 能否生成 v1/v5?
→ 本函数专注无状态 v4。需要 v1(带 MAC+时间)或 v5(基于命名空间哈希)请用 uuid 库。

Q3. SSR 会重复吗?
→ 不会。Node 的 crypto.randomUUID 同样加密级随机。

Q4. 要支持 IE11?
→ 需额外 crypto polyfill(如 crypto-js),但 IE 已正式退役,建议推动业务升级。

八、总结一句话

“先探测、后降级、零依赖、可 Tree-shaking”——把复杂度封装在工具里,业务代码永远只需 uuid()

如果本文对你有帮助,点个赞或在评论区交流更多“唯一序列号”玩法~

http://www.dtcms.com/a/434323.html

相关文章:

  • iBizModel 应用程序(PSSYSAPP)模型体系详解
  • iis 网站 起不来 temp文件夹html网站建设心得体会
  • Ubuntu防火墙端口管理指南
  • Ubuntu离线安装软件包
  • 山东电力建设网站泉州专业建站品牌
  • 微服务项目部署配置文件示例:从开发到生产的完整指南
  • 声卡驱动解决方案(电脑没有声音,麦克风没有声音)win11
  • 哈尔滨快速建站模板如意影院
  • 机器人、具身智能的起步——线性系统理论|【二】状态空间方程的解
  • 【数据结构】字典树
  • Rust Slint 实现控件拖动详细教程
  • 4. Pandas 数据选择、查询与修改
  • React新闻发布系统 角色列表篇
  • 网站常用插件wordpress自定义应用
  • 衡水手机网站建设淮安市盱眙县建设局网站
  • iPhone美区账号登录指南:轻松下载ChatGPT应用
  • AI大模型:(三)1.6 Dify工作流快速搭建数据可视化助手
  • 软件/网站安全需要以及解决方法
  • 做设备推广的网站学做网站用谁的书
  • python 做网站速度网站建设及托管合同
  • Coze源码分析-资源库-编辑工作流-后端源码-数据存储/安全/错误
  • 什么是Java反射机制?
  • 使用Docker安装Neo4j
  • 建立网站的步骤筝晃湖南岚鸿官网深圳专业建设网站哪个公司好
  • 20软件测试需求分析评审
  • SQL 多表查询实用技巧:ON 和 WHERE 的区别速览
  • 网站备案 内容央企八大设计院
  • 从汇编角度看C++优化:编译器真正做了什么
  • 分布式专题——25 深入理解网络通信和TCP、IP协议
  • UV python多版本管理