useId 是一個 React Hook,用於產生可傳遞給無障礙屬性的唯一 ID。

const id = useId()

參考

useId()

在組件的頂層呼叫 useId 來產生唯一的 ID

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

請參閱以下更多範例。

參數

useId 不接受任何參數。

回傳值

useId 會傳回與此特定元件中特定 useId 呼叫關聯的唯一 ID 字串。

注意事項

  • useId 是一個 Hook,因此您只能在組件的頂層或您自己的 Hooks 中呼叫它。您不能在迴圈或條件式中呼叫它。如果需要,請提取一個新的組件並將狀態移入其中。

  • useId 不應該用於在列表中產生 keyKey 應該從您的資料中產生。


用法

陷阱

不要使用 useId 來產生列表中的鍵值。 鍵值應該從您的資料中產生。

為無障礙屬性產生獨特的 ID

在組件的頂層呼叫 useId 來產生唯一的 ID

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
// ...

然後您可以將 產生的 ID 傳遞給不同的屬性

<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>

讓我們來看一個例子,了解何時可以使用它。

HTML 無障礙屬性,例如 aria-describedby,可讓您指定兩個標籤彼此相關。例如,您可以指定一個元素(例如輸入框)由另一個元素(例如段落)描述。

在一般的 HTML 中,您會這樣寫

<label>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
The password should contain at least 18 characters
</p>

然而,在 React 中,硬編碼 ID 並不是一個好習慣。一個組件可能會在頁面上渲染多次,但 ID 必須是唯一的!不要硬編碼 ID,而是使用 useId 產生一個獨特的 ID

import { useId } from 'react';

function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Password:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
The password should contain at least 18 characters
</p>
</>
);
}

現在,即使 PasswordField 在螢幕上出現多次,產生的 ID 也不會衝突。

import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  return (
    <>
      <label>
        Password:
        <input
          type="password"
          aria-describedby={passwordHintId}
        />
      </label>
      <p id={passwordHintId}>
        The password should contain at least 18 characters
      </p>
    </>
  );
}

export default function App() {
  return (
    <>
      <h2>Choose password</h2>
      <PasswordField />
      <h2>Confirm password</h2>
      <PasswordField />
    </>
  );
}

觀看此影片 以了解使用輔助技術的使用者體驗差異。

陷阱

使用 伺服器渲染 時,useId 要求伺服器和客戶端上的組件樹必須相同。如果您在伺服器和客戶端上渲染的樹不完全相同,則產生的 ID 將不匹配。

深入探討

為什麼 useId 比遞增計數器更好?

您可能會想知道為什麼 useId 比遞增全域變數(例如 nextId++)更好。

useId 的主要好處是 React 確保它能與 伺服器渲染 配合使用。在伺服器渲染期間,您的組件會產生 HTML 輸出。稍後,在客戶端上,hydration 會將您的事件處理程序附加到產生的 HTML。為了使 hydration 能正常運作,客戶端輸出必須與伺服器 HTML 相符。

使用遞增計數器很難保證這一點,因為客戶端組件 hydration 的順序可能與伺服器 HTML 發出的順序不符。透過呼叫 useId,您可以確保 hydration 能正常運作,並且伺服器和客戶端的輸出會相符。

在 React 內部,useId 是從呼叫組件的「父路徑」產生的。這就是為什麼如果客戶端和伺服器樹相同,「父路徑」將會匹配,無論渲染順序為何。


如果您需要為多個相關元素提供 ID,您可以呼叫 useId 為它們產生一個共用的前綴

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>
  );
}

這讓您可以避免為每個需要獨特 ID 的元素呼叫 useId


為所有產生的 ID 指定共用前綴

如果您在單一頁面上渲染多個獨立的 React 應用程式,請將 identifierPrefix 作為選項傳遞給您的 createRoothydrateRoot 呼叫。這可確保兩個不同應用程式產生的 ID 永遠不會衝突,因為使用 useId 產生的每個識別碼都會以您指定的不同前綴開頭。

import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

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 />);


在客戶端和伺服器端使用相同的 ID 前綴

如果您在同一個頁面上渲染多個獨立的 React 應用程式,並且其中一些應用程式是在伺服器端渲染的,請確保您在客戶端傳遞給 hydrateRoot 呼叫的 identifierPrefix 與您傳遞給 伺服器 API(例如 renderToPipeableStream)的 identifierPrefix 相同。

// Server
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(
<App />,
{ identifierPrefix: 'react-app1' }
);
// Client
import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(
domNode,
reactNode,
{ identifierPrefix: 'react-app1' }
);

如果頁面上只有一個 React 應用程式,則不需要傳遞 identifierPrefix

...(保留原文 HTML 連結結構)... useEffect