精读React hook(十四):总有一天你会需要useId为你生成唯一id

在开始介绍 useId 之前,有必要先介绍一下 React 的服务端渲染和客户端渲染之间的关系。当你看到一个服务端渲染的应用,它的渲染过程会是这样:服务端会先生成 HTML,然后将这个 HTML 发送到客户端,在客户端,React 会进行一个叫做 hydration 的过程,即将服务器端生成的 HTML 和客户端的 DOM 进行匹配,并生成最终的 HTML。

而在这个过程中,我们有时候需要给 DOM 生成唯一的 ID。例如:我们需要通过 JavaScript 或 CSS 选择器来访问 DOM 的时候;或者某些HTML属性(如 aria-labelledby)需要使用唯一的 ID 来关联元素。

如果在 hydration 过程中,服务器端和客户端生成的 ID 不一致,那么就会导致 hydration 失败。为了解决这个问题,React v18 引入了一个新的 Hook------useId 。通过使用一些内部机制,React 确保了无论是在服务器端还是客户端,对于同一个组件实例,useId 都会返回相同的 ID。在本文中,我们将探讨 useId 的使用方式和使用场景。

useId的使用

使用 useId 非常简单,这是它的用法定义:

jsx 复制代码
const id = useId()

它不需要任何参数。

基础用法

useId 会返回一个唯一的 ID,你可以将这个 ID 用于任何需要唯一 ID 的地方。这是一个使用 useId 的代码示例:

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

function MyComponent() {
  const uniqueId = useId();

  return (
    <div id={uniqueId}>
      Hello, 👉 weijunext.com
    </div>
  );
}

使用场景一:为可访问属性、无障碍属性生成唯一ID

如下面的例子,我们想关联 labelinput ,就需要在 <input> 元素上设置一个唯一的 id 属性;再在对应的 <label> 元素上设置 htmlFor 属性,其值与上述 id 相同。为了保证 id 唯一,就可以用 useId 来实现。

jsx 复制代码
import { useId } from "react";

export default function App() {
  const FullName = useId();
  const email = useId();

  return (
    <div className="card">
      <div>
        <label htmlFor={FullName}>Full Name</label>
        <input type="text" id={FullName} name="Full Name" />{" "}
      </div>
      <div>
        <label htmlFor={email}>Enter Email</label>
        <input type="email" id={email} name="email" />
      </div>
    </div>
  );
}

使用场景二:多次调用保证ID不重复

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

function PasswordField() {
  const passwordHintId = useId();
  return (
    <>
      <label>
        密码:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        密码应该包含至少 18 个字符
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <PasswordField />
      <PasswordField />
    </>
  );
}

当我们给组件使用 useId 后,即使这个组件被调用多次,也不会出现 id 重复的情况。

使用场景三:为相关元素生成统一前缀或后缀

有时候我们会想给相关元素(如表单项分类、列表元素)设置一个统一的前缀或后缀,那么这个前缀或后缀就可以用 useId 来生成。用法如下:

jsx 复制代码
import { useId } from "react";

export default function Example2() {
  let prefix1 = useId();
  let prefix2 = useId();

  return (
    <div className="card">
      <div>
        <label htmlFor={prefix1 + "-fullName"}>Full Name</label>
        <input type="text" id={prefix1 + "-fullName"} name="Full Name" />
      </div>
      <div>
        <label htmlFor={prefix1 + "-lastName"}>Last Name</label>
        <input type="text" id={prefix1 + "-lastName"} name="Last Name" />
      </div>

      <div>
        <label htmlFor={prefix2 + "-email"}>Enter Email</label>
        <input type="email" id={prefix2 + "-email"} name="email" />
      </div>
		 </div>
  );
}

在简单的场景下,这样做既可以减少 useId 的使用,又可以通过设置局部的唯一字符,实现 id 唯一的需求。

使用场景四:为应用生成全局统一的前缀或后缀

想象一个场景,我们想在一个单页面应用里渲染多个 React 应用,如下面这样:

jsx 复制代码
<!DOCTYPE html>
<html>
  <head><title>My app</title></head>
  <body>
    <div id="root1"></div>
    <div id="root2"></div>
  </body>
</html>

现在我们想给两个 React 应用都生成独立的唯一前缀,可以在 createRoot 调用中将 identifierPrefix 作为选项传递:

jsx 复制代码
const root1 = createRoot(document.getElementById('root1'), {
  identifierPrefix: 'my-first-app-'
});
root1.render(<App />);

const root2 = createRoot(document.getElementById('root2'), {
  identifierPrefix: 'my-second-app-'
});
root2.render(<App />);

总结

以上就是 useId 的使用方式和使用场景的总结,总的来说,useId 就是为所有可能需要唯一 ID 的场景而设计的,而这种特性在服务端渲染的应用里能把价值体现到最大。

系列文章列表

精读React hook(一):useState 的几个基础用法和进阶技巧

精读React hook(二):React状态管理的强大工具------useReducer

精读React hook(三):useContext从基础应用到性能优化

精读React hook(四):useRef的多维用途

精读React hook(五):useEffect使用细节知多少?

精读React hook(六):useLayoutEffect解决了什么问题?

精读React hook(七):用useMemo来减少性能开销

精读React hook(八):我们为什么需要useCallback

精读React hook(九):使用useTransition进行非阻塞渲染

精读React hook(十):使用useDeferredValue来做状态延迟更新

精读React hook(十一):useInsertionEffect------CSS-in-JS样式注入新方式

精读React hook(十二):使用useImperativeHandle能获得什么能力

精读React hook(十三):使用useSyncExternalStore获取实时数据

精读React hook(十四):总有一天你会需要useId为你生成唯一id

未完待续......

相关推荐
汪子熙18 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ26 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.4 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing6 小时前
【React】增量传输与渲染
前端·javascript·面试