React useId Hook

React 中有一个 useId hook,可以生成一个唯一 ID,这个有什么用处呢,用个 UUID 是不是可以替代呢?如果我们只考虑客户端,那么生成唯一 Id 的方法比较简单,我们在 State 中保存一个计数器就好,但是 React 需要支持服务端渲染,需要确保服务器生成的内容与客户端保持一致。客户端和服务器端是完全不同的环境,通过简单的计数器无法确保两端的一致。我们看下面这个例子,例子中要将 label 和 输入框进行关联,可以 useId 这个 hook 来实现。通过简单的计数器无法实现

复制代码
import { useId } from 'react';

export default function Form() {
  const id = useId();
  return (
    <form>
      <label htmlFor={id + '-firstName'}>First Name:</label>
      <input id={id + '-firstName'} type="text" />
      <hr />
      <label htmlFor={id + '-lastName'}>Last Name:</label>
      <input id={id + '-lastName'} type="text" />
    </form>
  );
}

React 是通过 dom tree 的位置来实现的,不同层有不同的前缀,如下:

复制代码
<div>
  <div id="1">
    <div id="101" />
  </div>
  <div id="10" />
</div>

下面代码 React 源码中的 Id 生成算法,通过位置进行移位,并对索引进行相加。

复制代码
export function pushTreeId(
  workInProgress: Fiber,
  totalChildren: number,
  index: number,
) {
  warnIfNotHydrating();

  idStack[idStackIndex++] = treeContextId;
  idStack[idStackIndex++] = treeContextOverflow;
  idStack[idStackIndex++] = treeContextProvider;

  treeContextProvider = workInProgress;

  const baseIdWithLeadingBit = treeContextId;
  const baseOverflow = treeContextOverflow;

  // The leftmost 1 marks the end of the sequence, non-inclusive. It's not part
  // of the id; we use it to account for leading 0s.
  const baseLength = getBitLength(baseIdWithLeadingBit) - 1;
  const baseId = baseIdWithLeadingBit & ~(1 << baseLength);

  const slot = index + 1;
  const length = getBitLength(totalChildren) + baseLength;

  // 30 is the max length we can store without overflowing, taking into
  // consideration the leading 1 we use to mark the end of the sequence.
  if (length > 30) {
    // We overflowed the bitwise-safe range. Fall back to slower algorithm.
    // This branch assumes the length of the base id is greater than 5; it won't
    // work for smaller ids, because you need 5 bits per character.
    //
    // We encode the id in multiple steps: first the base id, then the
    // remaining digits.
    //
    // Each 5 bit sequence corresponds to a single base 32 character. So for
    // example, if the current id is 23 bits long, we can convert 20 of those
    // bits into a string of 4 characters, with 3 bits left over.
    //
    // First calculate how many bits in the base id represent a complete
    // sequence of characters.
    const numberOfOverflowBits = baseLength - (baseLength % 5);

    // Then create a bitmask that selects only those bits.
    const newOverflowBits = (1 << numberOfOverflowBits) - 1;

    // Select the bits, and convert them to a base 32 string.
    const newOverflow = (baseId & newOverflowBits).toString(32);

    // Now we can remove those bits from the base id.
    const restOfBaseId = baseId >> numberOfOverflowBits;
    const restOfBaseLength = baseLength - numberOfOverflowBits;

    // Finally, encode the rest of the bits using the normal algorithm. Because
    // we made more room, this time it won't overflow.
    const restOfLength = getBitLength(totalChildren) + restOfBaseLength;
    const restOfNewBits = slot << restOfBaseLength;
    const id = restOfNewBits | restOfBaseId;
    const overflow = newOverflow + baseOverflow;

    treeContextId = (1 << restOfLength) | id;
    treeContextOverflow = overflow;
  } else {
    // Normal path
    const newBits = slot << baseLength;
    const id = newBits | baseId;
    const overflow = baseOverflow;

    treeContextId = (1 << length) | id;
    treeContextOverflow = overflow;
  }
}

这个Id 算法只有服务器渲染才需要,如果只是客户端渲染无需这么复杂的计算,React 也做了这样的处理,代码如下,客户端渲染直接使用全局计数器。

总结

useId 这个 Hook,是为了服务端渲染的需求才有的,客户端渲染无需这种复杂计算。不同层使用不同的前缀,通过移位进行区分。

相关推荐
kyriewen2 小时前
我把 10 道面试题答案真跑了一遍:6 道是错的
前端·javascript·面试
WebInfra3 小时前
Rspack 2.3 发布:联手 SWC 团队带来全新编译器
前端·javascript·github
FRing034 小时前
JavaScript入门到精通三十一(原型继承、原型链)
开发语言·前端·javascript
程序员大阳5 小时前
副队长JavaScript教程(52)-DOM操作元素的内容
javascript·dom·innertext·innerhtml·value·内容操作
裕波6 小时前
苏千|从 cnpm 到 Vite+:AI 时代的开发者体验演进
javascript
console.log('npc')6 小时前
周期任务功能完整实现:CRON 编辑器 + 模板变量 + 执行历史
javascript·vue.js·编辑器
程序员大阳6 小时前
副队长JavaScript教程(58)-基于DOM的网页留言板开发
javascript·实例·dom·案例·留言板
咚咚咚小柒6 小时前
【前端】LangChain.js学习
前端·javascript·人工智能·typescript·langchain·vue·ai编程
程序员大阳8 小时前
副队长JavaScript教程(55)-DOM节点增删改查
javascript·dom·增删改查·节点·克隆
程序0079 小时前
天地图实现收货地址定位
前端·javascript·css