Используйте useId() вместо вручную созданных ID

Источник: «Use useId() Instead Of Hand-Making IDs»
Мы считаем, что следует чаще использовать хук useId(). Если вы обнаружили, что не используете его, есть большая вероятность, что ваше приложение или сайт либо недоступны (a11y), либо вы создаёте код, склонный к ошибкам. Позвольте объяснить.

tl;dr


ID необходимы для создания ассоциаций в DOM для обеспечения доступности:

<div>
<label for="firstName">First Name</label>
<input type="text" id="firstName" />

<button aria-controls="modal">Open something</button>
<div id="modal"></div>
</div>

Если не используете атрибуты aria и role, то, к сожалению, ваш сайт, вероятно, не настолько доступен, как вы могли бы надеяться.

Другой популярный способ использования идентификаторов — обращение к DOM. У вас может быть div или input, к которому вам необходим прямой доступ, и для этого используется document.getElementByID():

useEffect(() => {
document.getElementById('firstName').focus()
}, [])

ID должны быть уникальными. Вы наверняка знаете, что

Проблема с ручным созданием ID заключается в том, что нет возможности убедиться в том, что создаёте что-то уникальное. Если JavaScript обращается к ID через document.getElementByID(), возникает ситуация, когда один из компонентов может обратиться к неправильному узлу DOM из-за дублирования ID.

Разработчики иногда пытаются использовать пространство имён для своих идентификаторов, чтобы обеспечить уникальность, как в случае с login-form-username:

function LoginForm() {
useEffect(() => {
document.getElementById('login-form-username').focus()
}, [])

return (
<form onSubmit={onSubmit}>
<label htmlFor="login-form-username">Username</label>
<input type="text" id="login-form-username" name="username" />

{/* ... */}
</form>
)
}

Это то, что я называю ID с пробелами в имени, потому что разработчик пытается создать уникальность, создавая более длинный ID, включающий что-то о странице или компоненте, встроенное в ID. Поначалу это может показаться прекрасным, но эта идея не работает для многих ситуаций в React, когда компонент используется дважды в DOM. Вы никогда не сможете использовать библиотеку компонентов, содержащую вкладки, выпадающие меню и другие многократно используемые компоненты, и зависящую от созданных вручную ID, потому что они непременно дадут сбой, когда компонент <DropDown /> будет дважды использоваться на странице.

Вместо этого используйте рефы

Для доступа к DOM из JavaScript используйте useRef(). Это безопасный способ доступа к DOM, масштабируемый на большие приложения с нулевой вероятностью создания дублирования, в результате которого ваши компоненты могут получить доступ к неправильному DOM.

A11y

Использование рефов вместо ID — отличная идея, но она подходит только для ситуаций, когда необходимо получить доступ к DOM из JavaScript. Как насчёт связывания двух узлов DOM для a11y? Ссылки не решают проблему, поэтому нужно создать ID. Для обеспечения уникальности можно попробовать использовать какую-нибудь стратегию, создающую уникальную строку. Что если использовать uuid() или другой алгоритм хэширования, или алгоритм создания уникальной строки?

function Comp() {
const uniqueId = uuid()

return (
<div>
<button aria-controls={uniqueId}>Open something</button>
<div id={uniqueId}></div>
</div>
)
}

Сначала можно подумать, что проблема заключается в повторном рендеринге, что необходимо запомнить это значение, чтобы оно оставалось неизменным во всех рендерах. На самом деле в данном случае это не проблема, потому что необходимо только связать два узла DOM, и неважно, изменится ли это значение.

Проблема этих стратегий заключается в повторной гидратации. Если бы использовался SSR, этот компонент генерировал бы один идентификатор на сервере и другой на клиенте. Когда он повторно гидрируется, это будет проблематично.

Заблуждение мы не используем SSR

Возможно, проект не использует SSR прямо сейчас, но я общаюсь со многими компаниями на воркшопах, и большинство из них разрабатывают некую внутреннюю стратегию повторного использования компонентов для нескольких проектов. Я говорю им, что это лишь вопрос времени, когда эти компоненты, пригодные для повторного использования, будут импортированы в проект, использующий SSR (например, Remix и Next), поэтому стоит следовать лучшим практикам SSR с самого начала.

Уникальные, не боящиеся гидратации ID

Причина существования useId() заключается в создании уникальных идентификаторов, передаваемых от SSR к клиенту. Когда в приложении много раз вызывается useId(), внутри него создаётся автоматически увеличивающееся число:

function MyComp() {
const first = useId()
console.log(first) // :r0:

const second = useId()
console.log(second) // :r1:
}

Если компонент получает :r0: и :r1: сейчас, но завтра произойдёт рефакторинг и другие компоненты до этого будут использовать useId(), то, возможно, вы получите :r4: и :r5:. Это нормально, нам не нужна предсказуемая строка, потому что мы просто связываем две вещи.

function Comp() {
const uniqueId = useId()

return (
<div>
<button aria-controls={uniqueId}>Open something</button>
<div id={uniqueId}></div>
</div>
)
}

Эта стратегия также работает, когда речь идёт об импорте кода вашего приложения в код чьей-то библиотеки. Если импортировать какой-нибудь компонент библиотеки UI, использующий useId(), то он будет прекрасно работать со всеми вашими вызовами useId().

Почему приложение недоступно

Причина, по которой я сказал, что ваше приложение недоступно или подвержено ошибкам, заключается в том, что вы либо принимаете a11y и aria, либо нет. Если не принимаете их, что ж, тогда приложение недоступно или, по крайней мере, гораздо менее доступно. Если используете a11y, но с вручную созданными ID, то, скорее всего, у вас код, склонный к ошибкам.

Не на React 18

Если нужно использовать useId(), то она доступна только в React 18. Однако несколько лет назад Ryan Florence сделал https://reach.tech, который фактически создал свой собственный useId() задолго до React 18. Кроме того, Ryan использует супер причудливые трюки для корректной повторной гидратации. Если вы ещё не перешли на React 18, то можете импортировать эту версию из Reach:

// npm install @reach/auto-id
import { useId } from '@reach/auto-id'

Несмотря на то, что Reach практически не поддерживается, этот пользовательский хук хорошо написан и будет служить вашим целям, пока не перейдёте на React 18.

Комментарии


Дополнительные материалы

Предыдущая Статья

Удаление дубликатов из массивов и строк в JavaScript

Следующая Статья

Гибкое кэширование в Laravel — это очень просто