扣丁书屋

为了生成唯一id,React18专门引入了新Hook:useId

看看如下组件有什么问题:

// App.tsx
const id = Math.random();

export default function App() {
  return <div id={id}>Hello</div>
}

如果应用是CSR(客户端渲染),id是稳定的,App组件没有问题。

但如果应用是SSR(服务端渲染),那么App.tsx会经历:

  1. React在服务端渲染,生成随机id(假设为0.1234),这一步叫dehydrate(脱水)
  2. <div id="0.12345">Hello</div>作为HTML传递给客户端,作为首屏内容
  3. React在客户端渲染,生成随机id(假设为0.6789),这一步叫hydrate(注水)

客户端、服务端生成的id不匹配!

事实上,服务端、客户端无法简单生成稳定、唯一的id是个由来已久的问题,早在15年就有人提过issue

Generating random/unique attributes server-side that don't break client-side mounting[1]

直到最近,React18推出了官方Hook——useId,才解决以上问题。他的用法很简单:

function Checkbox() {
  // 生成唯一、稳定id
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Do you like React?</label>
      <input type="checkbox" name="react" id={id} />
    </>
  );
);

虽然用法简单,但背后的原理却很有意思 —— 每个id代表该组件在组件树中的层级结构。

本文让我们来了解useId的原理。

React18来了,一切都变了

这个问题虽然一直存在,但之前一直可以使用自增的全局计数变量作为id,考虑如下例子:

// 全局通用的计数变量
let globalIdIndex = 0;


export default function App() {
  const id = useState(() => globalIdIndex++);
  return <div id={id}>Hello</div>
}

只要React在服务端、客户端的运行流程一致,那么双端产生的id就是对应的。

但是,随着React FizzReact新的服务端流式渲染器)的到来,渲染顺序不再一定。

比如,有个特性叫 Selective Hydration,可以根据用户交互改变hydrate的顺序。

当下图左侧部分在hydrate时,用户点击了右下角部分:

此时React会优先对右下角部分hydrate

关于Selective Hydration更详细的解释见:New Suspense SSR Architecture in React 18[2]

如果应用中使用自增的全局计数变量作为id,那么显然先hydrate的组件id会更小,所以id是不稳定的。

那么,有没有什么是服务端、客户端都稳定的标记呢?

答案是:组件的层次结构。

useId的原理

假设应用的组件树如下图:

不管BC谁先hydrate,他们的层级结构是不变的,所以「层级」本身就能作为服务端、客户端之间不变的标识。

比如B可以使用2-1作为idC使用2-2作为id

function B() {
  // id为"2-1"
  const id = useId();
  return <div id={id}>B</div>;
}

实际需要考虑两个要素:

1. 同一个组件使用多个id

比如这样:

function B() {
  const id0 = useId();
  const id1 = useId();
  return (
    <ul>
      <li id={id0}></li>
      <li id={id1}></li>
    </ul>
  );
}

2. 要跳过没有使用useId的组件

还是考虑这个组件树结构:

如果组件AD使用了useIdBC没有使用,那么只需要为AD划定层级,这样就能「减少需要表示层级」

useId的实际实现中,层级被表示为「32进制」的数。

之所以选择「32进制」,是因为选择尽可能大的进制会让生成的字符串尽可能紧凑。比如:

const a = 18;

// "10010" length 5
a.toString(2)   

//  "i" length 1
a.toString(32)  

具体的useId层级算法参考useId[3]

总结

React源码内部有多种结构(比如用于保存context数据的)。

useId``栈的逻辑是其中比较复杂的一种。

谁能想到用法如此简单的API背后,实现起来居然这么复杂?

React团队捣鼓「并发特性」,真挺不容易的...

参考资料

[1]Generating random/unique attributes server-side that don't break client-side mounting: https://github.com/facebook/react/issues/4000

[2]New Suspense SSR Architecture in React 18: https://github.com/reactwg/react-18/discussions/37

[3]useId: https://github.com/facebook/react/pull/22644


https://mp.weixin.qq.com/s/1dgCFFZ8Y2RltjwpZK_94g

深入 React 函数组件的 re-render 原理及优化

发布于:1年以前  |  615次阅读  |  详细内容 »

手写系列-实现一个铂金段位的 React

发布于:1年以前  |  484次阅读  |  详细内容 »

如何写出更优雅的 React 组件 - 代码结构篇

发布于:1年以前  |  581次阅读  |  详细内容 »

React 精品组件:mac-scrollbar

发布于:1年以前  |  549次阅读  |  详细内容 »

React 精品组件:mac-scrollbar

发布于:1年以前  |  530次阅读  |  详细内容 »

React 18 Beta 来了

发布于:1年以前  |  630次阅读  |  详细内容 »

手把手教你写一个 React 状态管理库

发布于:1年以前  |  568次阅读  |  详细内容 »

如何将 React 应用程序加载时间减少 70%

发布于:1年以前  |  618次阅读  |  详细内容 »

React 18 新特性之 startTransition

发布于:1年以前  |  586次阅读  |  详细内容 »

Reaction Engines成立新合资公司以打造紧凑、轻质的氨反应器

英国的Reaction Engines公司已经宣布成立一家合资企业以打造紧凑、轻质的氨反应器。这家公司表示,这种反应器可以用于航运和离网能源发电等困难行业的脱碳,并且还可以用于航空。

发布于:1年以前  |  639次阅读  |  详细内容 »

你不知道的 React 最佳实践

发布于:1年以前  |  515次阅读  |  详细内容 »

这才是 React Hooks 性能优化的正确姿势

发布于:1年以前  |  562次阅读  |  详细内容 »

所属标签

最多阅读

Android插件化方案 3年以前  |  235045次阅读
SourceTree,让你忘掉Git命令的工具 3年以前  |  3705次阅读
朴素贝叶斯分类器的应用 3年以前  |  3356次阅读
在Python的Django框架中包装视图函数 3年以前  |  3247次阅读
用Sublime打造Protobuf迷你IDE 3年以前  |  3241次阅读
Python贪吃蛇游戏编写代码 3年以前  |  3224次阅读
在Sublime中高亮显示Proto Buffer 3年以前  |  3222次阅读
Google Shell 风格指南 3年以前  |  3110次阅读
Google Python 风格指南 3年以前  |  3097次阅读
Genymotion下载及安装使用 3年以前  |  3045次阅读
Markdown语法 转义 3年以前  |  2990次阅读
Markdown语法 链接 3年以前  |  2860次阅读
Markdown语法 段落内代码 3年以前  |  2814次阅读
java中NIO与传统IO 3年以前  |  2769次阅读

手机扫码阅读