Основы Vue: понимание useId во Vue 3

Источник: Telerik Blogs•

Основы Vue: понимание useId во Vue 3

Узнайте, как использовать useId во Vue 3 для генерации стабильных, уникальных ID для переиспользуемых компонентов, улучшения доступности и предотвращения проблем с гидратацией SSR.

Узнайте, как использовать useId во Vue 3 для генерации стабильных, уникальных ID для переиспользуемых компонентов, улучшения доступности и предотвращения проблем с гидратацией SSR.

Если вы когда-либо создавали переиспользуемый компонент формы во Vue, вы, вероятно, прописывали id вручную, не задумываясь. Для одного поля формы это работает должным образом: метка правильно связывается с инпутом, а клик по метке фокусирует поле.

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

То, что отлично работало один раз, быстро может стать проблемой, когда один и тот же компонент рендерится несколько раз. Внезапно каждый экземпляр создает один и тот же id, оставляя вас с дублирующимися ID, нарушенными связями меток и компонентами, которые больше не обладают необходимой доступностью.

Распространенная реакция — самостоятельно генерировать ID с помощью Math.random(), счетчика или библиотеки UUID. Хотя эти подходы решают одну проблему, они могут породить другую, особенно в приложениях, использующих серверный рендеринг.

Здесь на помощь приходит функция useId из Vue.

Вместо того чтобы заставлять вас управлять ID вручную, useId генерирует уникальные, стабильные идентификаторы, которые бесшовно работают в переиспользуемых компонентах и при серверном рендеринге. В этой статье вы узнаете, зачем она существует, как она работает и когда ее стоит применять в собственных приложениях.

Жесткое кодирование id само по себе не является ошибкой. На самом деле, это самое простое решение, когда вы создаете одноразовую страницу на Vue. Сложность начинается тогда, когда вы превращаете эту разметку в переиспользуемый компонент.

Рассмотрим этот простой компонент BaseInput:

Если отрендерить компонент один раз, всё работает как ожидается.

Теперь представьте, что вы используете тот же компонент для создания формы регистрации.

Каждый экземпляр компонента рендерит один и тот же id, оставляя вашу страницу с несколькими элементами, которые выглядят следующим образом:

Это создает невалидный HTML, поскольку id должен быть уникальным в пределах страницы. Что еще важнее, это нарушает связь между меткой и соответствующим ей инпутом. Клик по метке может больше не фокусировать нужное поле, а вспомогательные технологии, такие как программы чтения с экрана, могут испытывать трудности с определением того, какая метка принадлежит какому инпуту.

Эту проблему можно решить, передавая уникальный id каждому экземпляру компонента, но это также означает, что у каждого разработчика, использующего ваш компонент, появляется еще одна вещь, которую нужно держать в голове. Забудьте об этом один раз или случайно переиспользуйте то же значение, и проблема вернется.

В идеале переиспользуемый компонент должен справляться с этим за вас. Вопрос в том, как автоматически генерировать уникальные ID, не усложняя использование компонента?

Именно эту задачу мы решим далее.

Почему распространенные решения не справляются

Как только вы решили, что жесткое кодирование id не подходит для переиспользуемого компонента, следующий шаг кажется очевидным: сгенерировать его автоматически.

Распространенный подход — использование Math.random().

Другой вариант — использование библиотеки UUID.

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

Однако современные приложения на Vue не всегда рендерятся исключительно на клиенте. Такие фреймворки, как Nuxt, часто сначала рендерят компоненты на сервере, а затем гидратируют их в браузере. Чтобы гидратация работала корректно, HTML, сгенерированный на сервере, должен совпадать с тем, что ожидает Vue на клиенте.

Генераторы случайных ID не могут гарантировать такую согласованность. Каждый рендер производит новое значение, поэтому сервер и клиент могут сгенерировать разные ID для одного и того же компонента. Именно здесь могут начаться проблемы с гидратацией.

Цель состоит не просто в генерации уникальных ID — нужно генерировать ID, которые являются одновременно уникальными и предсказуемыми в различных средах рендеринга.

Именно эту проблему призван решить useId.

Понимание useId

Теперь, когда мы увидели, почему ручное управление ID плохо масштабируется, давайте рассмотрим решение от Vue.

useId — это утилита Composition API, которая генерирует уникальный стабильный ID для текущего экземпляра компонента. Вместо создания ID вручную, вы просто запрашиваете его у Vue и используете там, где вашему компоненту нужен идентификатор.

Вот базовый синтаксис:

Каждый раз, когда рендерится новый экземпляр этого компонента, Vue генерирует новый ID. Это означает, что вы можете рендерить компонент столько раз, сколько захотите, не беспокоясь о дублировании ID.

Что отличает useId от Math.random() или генератора UUID — это не только уникальность ID, но и то, что Vue контролирует процесс их генерации. Поскольку фреймворк управляет процессом рендеринга, он может создавать ID, которые остаются согласованными в различных средах рендеринга, что делает их пригодными для переиспользуемых компонентов и приложений с серверным рендерингом.

Стоит также отметить, для чего useId не предназначен.

Возвращаемое им значение предназначено для DOM. Используйте его для связывания меток с инпутами, ассоциации вспомогательного текста с полями форм или создания связей между HTML-элементами. Он не предназначен для замены ID баз данных, идентификаторов API или ключей v-for.

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

Теперь, когда мы понимаем, что он делает, давайте посмотрим, как он вписывается в реальный компонент, который вы вероятно будете создавать почти в каждом приложении: переиспользуемый компонент инпута.

Практический пример: создание переиспользуемого компонента инпута

Давайте применим useId на практике, создав переиспользуемый компонент инпута.

Мы начнем с простой версии, которая принимает метку и рендерит поле ввода:

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

Проблема появляется при повторном использовании компонента.

Поскольку каждый экземпляр рендерит один и тот же id, все три инпута в итоге используют "input" в качестве своего идентификатора. Это ровно та проблема, которую мы обсуждали ранее.

Вместо того чтобы заставлять каждого разработчика передавать уникальный id, мы можем позволить компоненту сгенерировать его автоматически с помощью useId

Теперь каждый экземпляр BaseInput получает собственный уникальный идентификатор. Компонент остается таким же простым в использовании, но вам больше не нужно самостоятельно генерировать или управлять ID.

Шаг вперед

Большинству переиспользуемых компонентов инпутов требуется нечто большее, чем просто метка. Часто требуется отображать вспомогательный текст, предоставляющий пользователю дополнительные инструкции.

Например, вы можете захотеть объяснить требования к паролю.

Чтобы сделать это доступным, вспомогательный текст должен быть связан с инпутом с помощью атрибута aria-describedby. Это позволяет программам чтения с экрана озвучивать дополнительную информацию, когда инпут получает фокус.

С useId создание этой связи становится очень простым:

Здесь у каждого элемента есть четкое назначение:

  • inputId связывает метку и инпут.
  • helperTextId связывает вспомогательный текст с инпутом.
  • Каждый экземпляр компонента автоматически получает свой набор ID.

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

И самое лучшее то, что вся эта сложность скрыта от разработчика, использующего ваш компонент.

Снаружи компонент имеет понятный и простой API. Изнутри он управляет генерацией идентификаторов, необходимых для правильной связи всех элементов.

В этом заключается одно из главных преимуществ useId. Оно позволяет вашим компонентам управлять деталью реализации, о которой разработчикам, использующим компонент, думать не нужно.

Почему useId безопасен для SSR

Ранее мы вкратце упомянули, что генераторы случайных ID могут вызывать проблемы в приложениях, использующих серверный рендеринг (SSR). Теперь, когда вы увидели useId в действии, понять почему стало проще.

Когда приложение использует SSR, Vue рендерит ваши компоненты на сервере для создания начального HTML. Затем этот HTML отправляется в браузер, где Vue выполняет «гидратацию» (hydration), добавляя интерактивность.

Чтобы гидратация прошла успешно, HTML, сгенерированный на сервере, должен совпадать с тем, что Vue ожидает отрендерить на клиенте. Генераторы случайных ID этого не гарантируют.

Представьте, что этот компонент использует Math.random() для создания ID. Сервер может сгенерировать:

Когда браузер рендерит тот же компонент во время гидратации, он может сгенерировать:

Компонент не изменился, но ID больше не совпадают. Теперь Vue должен устранить это несоответствие, что может привести к предупреждениям гидратации или ненужным обновлениям DOM.

useId избегает этой проблемы, предсказуемо генерируя ID. Поскольку Vue контролирует процесс рендеринга, он может гарантировать, что один и тот же компонент получит одинаковый ID как при серверном рендеринге, так и при клиентской гидратации.

Главный вывод здесь не в том, что вам нужно понимать внутреннее устройство SSR. А в том, что useId позволяет создавать повторно используемые компоненты, не беспокоясь о том, продолжит ли код корректно работать в приложениях с серверным рендерингом.

Когда использовать (и избегать) useId

Как и большинство утилит Vue, useId решает конкретную задачу. Вы не будете использовать его в каждом компоненте, но когда он нужен, он избавляет от удивительно большого объема ручной работы.

Хорошее правило гласит: если вашему компоненту нужно создать связь между элементами DOM с помощью id, useId — обычно правильный выбор.

К распространенным примерам относятся:

  • Связывание <label> с <input>
  • Подключение текста-подсказки или сообщений об ошибках с помощью aria-describedby
  • Связывание элементов с атрибутами доступности, такими как aria-describedby
  • Создание повторно используемых компонентов форм, которые могут отображаться несколько раз на одной странице

С другой стороны, useId не предназначен для данных приложения или бизнеса. Например, его не следует использовать для:

  • ID записей в базе данных
  • Номеров заказов или счетов
  • Идентификаторов ресурсов API
  • Ключей v-for
  • Всего, что нужно хранить или передавать за пределами компонента

Эти значения представляют данные вашего приложения, в то время как useId существует исключительно для генерации стабильных ID для DOM.

Если вы не уверены, стоит ли его использовать, задайте себе простой вопрос:

«Я идентифицирую данные приложения или соединяю элементы HTML?»

«Я идентифицирую данные приложения или соединяю элементы HTML?»

Если речь идет о соединении элементов в DOM, то useId, скорее всего, будет правильным выбором. Если же речь идет об идентификации бизнес-данных, вам следует использовать идентификатор, созданный специально для этой цели.

Лучшие практики для useId

Прежде чем начать использовать useId во всем приложении, помните об этих практических рекомендациях:

  • Используйте useId только тогда, когда вашему компоненту нужен ID в DOM. Он идеально подходит для связывания меток, полей ввода, вспомогательного текста и других атрибутов доступности.
  • Генерируйте ID внутри компонента. Позвольте компоненту самому управлять своими деталями реализации вместо того, чтобы ожидать уникальные ID от каждого места использования.
  • Используйте один и тот же ID повторно для связанных элементов. Метка (label) и соответствующее ей поле ввода должны ссылаться на один и тот же идентификатор. Генерируйте дополнительные ID только тогда, когда создаете другую связь, например, связывая вспомогательный текст с помощью aria-describedby.
  • Не используйте useId для данных приложения. Записи баз данных, ресурсы API и ключи v-for имеют разные требования, и для них следует использовать идентификаторы, предназначенные для этих целей.
  • Помните о доступности. useId — это не просто способ избежать дублирования ID, это еще и простой способ создавать компоненты, которыми всем будет проще пользоваться.

Эти практики помогут вам писать компоненты, которые проще повторно использовать и поддерживать, и которые будут готовы к более сложным приложениям по мере роста вашего проекта.

Итоги

На первый взгляд, useId кажется небольшой утилитой. Она генерирует ID, и на этом все. Но как только вы начинаете создавать повторно используемые компоненты, вы быстро понимаете, что она решает гораздо большую проблему, чем просто предотвращение дублирования ID. Она устраняет повторяющиеся детали реализации и помогает сохранять доступность и согласованность — даже в приложениях, использующих серверный рендеринг.

Что еще важнее, useId отражает более широкую идею, которую вы встретите во всем Vue: фреймворк берет на себя общие проблемы, чтобы вы могли сосредоточиться на создании функций, а не на изобретении инфраструктуры заново.

В следующий раз, когда вы обнаружите, что жестко кодируете id внутри повторно используемого компонента, задумайтесь, не должен ли компонент сам отвечать за его генерацию. Во многих случаях useId был создан именно для этого.

О чём эта статья