useId
Хук useId
позволяет создавать уникальные идентификаторы, которые затем можно использовать, например, в атрибутах доступности.
const id = useId()
Справочник
useId()
Чтобы создать уникальный идентификатор, вызовите useId
на верхнем уровне своего компонента:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
Параметры
useId
не принимает параметров.
Возвращаемое значение
useId
возвращает уникальный идентификатор, привязанный к данному конкретному вызову useId
в данном конкретном компоненте.
Замечания
-
useId
— это хук, поэтому его нужно вызывать только на верхнем уровне вашего компонента или хука. Его нельзя вызывать внутри циклов и условий. Если это всё же для чего-то нужно, выделите этот вызов в отдельный компонент, который затем можно рендерить по условию или в цикле. -
useId
не должен использоваться для создания ключей в списках. Ключи должны выбираться на основе данных.
Применение
Создание уникальных идентификаторов для атрибутов доступности
Чтобы получить уникальный идентификатор, вызовите useId
на верхнем уровне своего компонента:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
После чего вы можете указывать этот сгенерированный идентификатор в различных атрибутах:
<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>
Разберём на примере, когда это может быть полезно.
HTML-атрибуты доступности, такие как aria-describedby
, позволяют обозначать смысловую связь между тегами. Например, можно указать, что некоторый элемент разметки (абзац) содержит краткое описание другого элемента (поля ввода).
В обычном HTML вы могли бы описать это так:
<label>
Пароль:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
Пароль должен содержать не менее 18 символов
</p>
Однако в React подобным образом фиксировать идентификаторы в коде — не лучшая практика. Один и тот же компонент может использоваться в нескольких разных местах — но ведь в каждом случае идентификаторы должны быть уникальны! Поэтому вместо фиксированного идентификатора лучше сгенерировать уникальный с помощью useId
:
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Пароль:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
Пароль должен содержать не менее 18 символов
</p>
</>
);
}
В итоге, сгенерированные идентификаторы не будут конфликтовать, даже если использовать PasswordField
на экране в нескольких местах.
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 ( <> <h2>Выберите пароль</h2> <PasswordField /> <h2>Подтвердите пароль</h2> <PasswordField /> </> ); }
Посмотрите видео-демонстрацию о том, как всё это влияет на опыт использования вспомогательных технологий.
Deep Dive
Возможно, вы задаётесь вопросом, почему для получения нового идентификатора лучше использовать useId
, а не увеличивать постоянно некий глобальный счётчик — nextId++
.
Главное преимущество useId
в том, что React гарантирует его корректную работу с серверным рендерингом. В процессе серверного рендеринга ваши компоненты создают HTML, к которому затем на клиенте при гидратации подключаются ваши обработчики событий. Чтобы гидратация сработала правильно, клиентский вывод должен совпасть с полученным от сервера HTML.
Однако крайне трудно быть уверенным, что они совпадут, если пользоваться обычным инкрементируемым счётчиком. Ведь порядок гидратации клиентских компонентов может не совпадать с порядком, в котором HTML составлялся на сервере. Используя же useId
, вы гарантируете, что созданные идентификаторы на сервере и на клиенте будут совпадать, и гидратация выполнится правильно.
Внутри React useId
вычисляется на основе “пути из цепочки родителей” того компонента, который вызывает useId
. А если отрендеренные на сервере и на клиенте деревья компонентов совпадают, то и полный “путь из цепочки родителей” для каждого компонента будет совпадать, в каком бы порядке они не рендерились.
Создание идентификаторов для нескольких элементов
Если вам нужны разные идентификаторы для нескольких элементов, и эти элементы как-то связаны по смыслу, то с помощью useId
вы можете создать один общий для всех префикс:
import { useId } from 'react'; export default function Form() { const id = useId(); return ( <form> <label htmlFor={id + '-firstName'}>Имя:</label> <input id={id + '-firstName'} type="text" /> <hr /> <label htmlFor={id + '-lastName'}>Фамилия:</label> <input id={id + '-lastName'} type="text" /> </form> ); }
Так можно обойтись без лишних вызовов useId
на каждый элемент, которому понадобился идентификатор.
Задание общего префикса для всех идентификаторов вообще
Если вы отображаете несколько независимых React-приложений на одной странице, то в вызовах createRoot
и hydrateRoot
вы можете указать опцию identifierPrefix
. Поскольку все полученные из 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 />);
Using the same ID prefix on the client and the server
If you render multiple independent React apps on the same page, and some of these apps are server-rendered, make sure that the identifierPrefix
you pass to the hydrateRoot
call on the client side is the same as the identifierPrefix
you pass to the server APIs such as 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' }
);
You do not need to pass identifierPrefix
if you only have one React app on the page.