Хук useId в React служит для создания уникального идентификатора (id). Формат сгенерированного идентификатора — двоеточие, за которым следует буква «r», затем число и, наконец, еще одно двоеточие (например, «:r1:», «:r2:»).

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

export default function App() {
  return (
    <div>
      <label>
        <span>Some Input</span>
        <input name="some-input" />
      </label>
      <br />
      <label>
        <span>Some Checkbox</span>
        <input name="some-checkbox" type="checkbox" />
      </label>
    </div>
  );
}

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

Хук useId решает эту проблему, генерируя уникальное и абсурдное значение id. Это освобождает нас от необходимости придумывать осмысленные имена переменных или идентификаторы. Хук React useId гарантирует, что сгенерированный идентификатор уникален внутри компонента.

export default function App() {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>Some Input</label>
      <img src="./some_icon.svg" alt="icon" />
      <input name="some-input" id={id} />
    </div>
  )
}

Теперь давайте рассмотрим, почему хук useId является неотъемлемой частью экосистемы React. Одной из причин является рендеринг на стороне сервера (SSR). React может работать не только на клиенте, но и на сервере. Если бы мы сгенерировали идентификатор с помощью такой библиотеки, как uuid, значение идентификатора на сервере отличалось бы от значения идентификатора на клиенте. Это несоответствие может привести к ошибкам и несоответствиям.

Хук React useId решает эту проблему, предоставляя один и тот же идентификатор как на сервере, так и на клиенте во время SSR. Это достигается за счет использования общего treeId в качестве базового идентификатора. Обратите внимание, что идентификатор, сгенерированный useId, уникален только внутри компонента. Если в компоненте используется несколько хуков useId, они будут использовать один и тот же treeId.

Реализация хука useId в React включает две функции: mountId и updateId. Первый вызывается при первоначальном отображении или монтировании компонента, а второй вызывается при последующих обновлениях. Такое разделение позволяет создавать более гибкий код без необходимости использования чрезмерной условной логики.

Функция mountId генерирует идентификатор, комбинируя treeId и локальный счетчик, специфичный для текущего компонента. Этот счетчик увеличивается каждый раз, когда хук useId используется внутри компонента, обеспечивая уникальность внутри этого компонента.

function mountId(): string {
  const hook = mountWorkInProgressHook();

  const root = ((getWorkInProgressRoot(): any): FiberRoot);
  // TODO: In Fizz, id generation is specific to each server config. Maybe we
  // should do this in Fiber, too? Deferring this decision for now because
  // there's no other place to store the prefix except for an internal field on
  // the public createRoot object, which the fiber tree does not currently have
  // a reference to.
  const identifierPrefix = root.identifierPrefix;

  let id;
  if (getIsHydrating()) {
    const treeId = getTreeId();

    // Use a captial R prefix for server-generated ids.
    id = ':' + identifierPrefix + 'R' + treeId;

    // Unless this is the first id at this level, append a number at the end
    // that represents the position of this useId hook among all the useId
    // hooks for this fiber.
    const localId = localIdCounter++;
    if (localId > 0) {
      id += 'H' + localId.toString(32);
    }

    id += ':';
  } else {
    // Use a lowercase r prefix for client-generated ids.
    const globalClientId = globalClientIdCounter++;
    id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
  }

  hook.memoizedState = id;
  return id;
}

function updateId(): string {
  const hook = updateWorkInProgressHook();
  const id: string = hook.memoizedState;
  return id;
}

Для одностраничных приложений (SPA) глобальный счетчик используется для генерации идентификаторов во всем приложении React. Этот счетчик гарантирует, что каждый хук useId получит уникальный идентификатор. Однако важно отметить, что когда компонент, такой как модальное окно, монтируется несколько раз, каждый раз будет генерироваться новый идентификатор. Нет механизма сохранения предыдущего идентификатора.

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