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

🧑‍💻
推荐全栈学习资源:
  • Next.js 中文文档:样式和官网一样的中文文档,创造沉浸式Next.js中文学习体验。
  • 《Chrome插件全栈开发》:真实出海项目的实战教学课,讲解Chrome插件和Next.js端的全栈开发,帮助你半个月内成为全栈出海工程师。
  • 在开始介绍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非常简单,这是它的用法定义:

    const id = useId()

    它不需要任何参数,返回的是一个唯一的字符串 ID。

    基础用法

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

    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来实现。

    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不重复

    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来生成。用法如下:

    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 应用,如下面这样:

    <!DOCTYPE html>
    <html>
      <head><title>My app</title></head>
      <body>
        <div id="root1"></div>
        <div id="root2"></div>
      </body>
    </html>

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

    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 Hooks

    专栏演示站:React Hooks Demos

    专栏源码仓库:👉Github - Source Code

    交个朋友:👉加入「独立全栈交流群」

    专栏文章列表:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    精读React hooks(十五):把useDebugValue加入你的React调试工具库

    精读React hooks(十六):一个为代码优雅而生的hook——use