useId
useId — это React Hook для генерации уникальных ID, которые можно передавать в accessibility-атрибуты.
const id = useId()Справочник
useId()
Вызывайте useId на верхнем уровне вашего компонента, чтобы сгенерировать уникальный ID:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...Параметры
useId не принимает никаких параметров.
Возвращаемое значение
useId возвращает уникальную строку ID, связанную с конкретным вызовом useId в этом конкретном компоненте.
Предостережения
-
useId— это Hook, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или в собственных Hooks. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, вынесите новый компонент и перенесите state в него. -
useIdне следует использовать для генерации cache keys для use(). ID стабилен, пока компонент смонтирован, но может изменяться во время rendering. Cache keys следует генерировать из ваших данных. -
useIdне следует использовать для генерации keys в списке. Keys должны генерироваться из ваших данных. -
useIdв настоящее время нельзя использовать в async Server Components.
Использование
Не вызывайте useId для генерации keys в списке. Keys должны генерироваться из ваших данных.
Генерация уникальных ID для accessibility-атрибутов
Вызывайте useId на верхнем уровне вашего компонента, чтобы сгенерировать уникальный ID:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...Затем вы можете передать сгенерированный ID в разные атрибуты:
<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>Давайте разберём пример, чтобы увидеть, когда это полезно.
HTML accessibility-атрибуты вроде aria-describedby позволяют указать, что два тега связаны друг с другом. Например, можно указать, что один элемент (например, input) описывается другим элементом (например, абзацем).
В обычном HTML вы бы написали так:
<label>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
The password should contain at least 18 characters
</p>Однако жёстко захардкоженные ID — не лучшая практика в React. Компонент может быть отрендерен на странице более одного раза, а ID должны быть уникальными! Вместо того чтобы задавать ID вручную, сгенерируйте уникальный ID с помощью useId:
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 />
</>
);
}При server rendering useId требует идентичного дерева компонентов на сервере и на клиенте. Если деревья, которые вы рендерите на сервере и на клиенте, не совпадают в точности, сгенерированные ID тоже не совпадут.
Почему useId лучше, чем увеличивающийся счётчик?
Вы можете задаться вопросом, почему useId лучше, чем увеличение глобальной переменной вроде nextId++.
Главное преимущество useId в том, что React гарантирует его совместимость с server rendering. Во время server rendering ваши компоненты генерируют HTML-вывод. Позже, на клиенте, hydration привязывает обработчики событий к сгенерированному HTML. Чтобы hydration работал, клиентский вывод должен совпадать с HTML, полученным с сервера.
Это очень трудно гарантировать с увеличивающимся счётчиком, потому что порядок, в котором Client Components гидратируются, может не совпадать с порядком, в котором серверный HTML был выведен. Используя useId, вы гарантируете, что hydration сработает, а вывод на сервере и клиенте будет совпадать.
Внутри React useId генерируется из "parent path" вызывающего компонента. Поэтому, если дерево на клиенте и на сервере одинаковое, "parent path" совпадёт независимо от порядка rendering.
Генерация ID для нескольких связанных элементов
Если вам нужно назначить 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>
);
}Так вы избегаете вызова useId для каждого отдельного элемента, которому нужен уникальный ID.
Указание общего префикса для всех сгенерированных ID
Если вы рендерите несколько независимых React-приложений на одной странице, передайте identifierPrefix как опцию в вызовы createRoot или hydrateRoot. Это гарантирует, что ID, сгенерированные двумя разными приложениями, никогда не конфликтуют, потому что каждый идентификатор, созданный с помощью useId, будет начинаться с указанного вами уникального префикса.
//index.html
<!DOCTYPE html>
<html>
<head><title>My app</title></head>
<body>
<div id="root1"></div>
<div id="root2"></div>
</body>
</html>Использование одного и того же префикса ID на клиенте и сервере
Если вы рендерите несколько независимых React-приложений на одной странице, и некоторые из этих приложений серверно-рендерятся, убедитесь, что identifierPrefix, который вы передаёте в hydrateRoot на стороне клиента, совпадает с identifierPrefix, который вы передаёте в server APIs вроде renderToPipeableStream.
// 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 не нужно.