使用 Web Crypto API 生成唯一 ID
每个前端开发者最终都会遇到这个问题:你需要为客户端数据生成一个唯一标识符,但还没有服务器生成的 ID。也许你正在构建一个待办事项列表、管理表单字段,或者在数据持久化之前跟踪项目。你需要一个可靠的解决方案,而且现在就需要。
好消息是什么?现代浏览器内置了解决方案。Web Crypto API 提供了 crypto.randomUUID()——一种简单、安全的方式,无需安装任何库即可在浏览器中生成唯一 ID。
核心要点
crypto.randomUUID()在浏览器中原生生成符合 RFC 标准、加密安全的 v4 UUID,零依赖。- 始终在对象创建时生成 ID,而不是在渲染周期中生成。
- 该方法需要安全上下文(HTTPS 或
localhost)。 - 对于不支持
crypto.randomUUID()的环境,可使用crypto.getRandomValues()作为轻量级回退方案。
为什么 crypto.randomUUID() 应该是你的默认选择
crypto.randomUUID() 方法使用加密安全的随机数生成器生成符合 RFC 9562(原 RFC 4122)标准的 version-4 UUID。输出如下所示:
550e8400-e29b-41d4-a716-446655440000
这是一个包含 122 位随机性的 36 字符字符串。碰撞概率极低——你需要以每秒 10 亿个 UUID 的速度生成约 85 年,才能达到 50% 的单次碰撞概率。
使用方法非常简单:
function generateId() {
return crypto.randomUUID()
}
const newItem = { id: generateId(), label: 'My item' }
该方法在所有现代浏览器中都可用(Chrome 92+、Firefox 95+、Safari 15.4+),可在 Web Workers 中运行,且无需任何依赖。
安全上下文要求
一个关键细节:crypto.randomUUID() 只能在安全上下文中工作。这意味着生产环境需要 HTTPS,或者开发期间使用 localhost。如果你在非 localhost 域名上通过纯 HTTP 进行测试,该方法将不可用。
这个要求的存在是因为 Web Crypto API 提供的加密原语不应在不安全的环境中暴露。
为什么旧模式存在不足
在 crypto.randomUUID() 广泛可用之前,开发者经常使用看似合理但实际存在问题的替代方案。
Math.random() 不具备加密安全性。其输出可能是可预测的,在生产应用中碰撞的可能性远高于预期。
时间戳(如 Date.now())在同一毫秒内生成多个 ID 时会失效——这在循环或批量操作中是常见场景。
临时生成器结合时间戳和随机数会更好,但它们是在用更低的严谨性重新发明一个已解决的问题,而平台已经原生提供了更好的方案。
对于安全的客户端 ID 生成,crypto.randomUUID() 是明确的选择。
Discover how at OpenReplay.com.
前端实践注意事项
生成一次,立即存储
最常见的错误是在渲染周期中生成 ID。正确的做法是在创建对象时生成 ID 并存储:
// ✓ 正确:在创建时生成 ID
function addItem(label) {
return { id: crypto.randomUUID(), label }
}
// ✗ 错误:在渲染时生成 ID
items.map(item => <li key={crypto.randomUUID()}>{item.label}</li>)
在每次渲染时生成新的 UUID 违背了稳定 key 的目的。例如,React 使用 key 在重新渲染时跟踪元素。每次使用新 key 会强制进行不必要的 DOM 拆卸和重建。
SSR 和浏览器环境
如果你使用服务端渲染,crypto.randomUUID() 在 Node.js 19+ 和其他运行时的最新版本中可用。对于较旧的 Node 版本或边缘情况,在调用前检查可用性:
const id = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
? crypto.randomUUID()
: fallbackGenerator()
轻量级回退策略
对于不支持 crypto.randomUUID() 的环境,crypto.getRandomValues() 具有更广泛的支持(包括 Node.js 16+),可以在不使用库的情况下生成 UUID:
function fallbackUUID() {
const bytes = crypto.getRandomValues(new Uint8Array(16))
bytes[6] = (bytes[6] & 0x0f) | 0x40 // Version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80 // Variant 10
const hex = [...bytes].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
}
这会手动设置版本和变体位以生成有效的 v4 UUID,底层使用相同的加密安全随机源。
何时仍需使用库
虽然 crypto.randomUUID() 涵盖了大多数情况,但在以下需求时你可能会选择 uuid 或 nanoid 等库:
- 非 v4 版本的 UUID(v1、v5、v7)
- 用于 URL 或面向用户显示的更短 ID
- 保证与旧运行时的跨环境兼容性
但对于标准的安全客户端 ID 生成,原生 API 应该是你的起点。
结论
crypto.randomUUID() 为你提供符合 RFC 标准、加密安全的 UUID,零依赖。在对象创建时生成 ID,而不是在渲染时。生产环境使用 HTTPS。对于边缘情况,回退到 crypto.getRandomValues()。浏览器已经提供了你需要的功能——使用它。
常见问题
是的,crypto.randomUUID() 生成的 v4 UUID 适合作为主键。122 位的加密随机性使碰撞几乎不可能发生。但要注意,随机 UUID 在某些数据库中可能导致索引碎片化。如果大规模插入性能很重要,可以考虑按时间排序的 UUID v7,不过这需要使用库。
它需要安全上下文是因为 Web Crypto API 暴露了加密原语。浏览器将这些限制在 HTTPS 和 localhost,以防止中间人攻击篡改加密操作。在 localhost 上进行本地开发时,无需 HTTPS 即可工作。在生产环境中,你需要有效的 TLS 证书。
可以。只要上下文是安全的,crypto 对象及其 randomUUID 方法在 Web Workers、Service Workers 和 Shared Workers 中都可用。这使得在后台线程中生成 ID 变得很方便,无需与主线程通信来创建 ID。
crypto.randomUUID() 使用浏览器内置的加密随机生成器生成标准的 36 字符 v4 UUID。nanoid 生成更短、URL 友好的 ID,具有可自定义的字母表和长度,并且开箱即用地支持更多环境。选择 randomUUID 以获得标准合规性和零依赖。选择 nanoid 当你需要紧凑的 ID 或更广泛的运行时支持时。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.