useId
는 접근성 속성에 전달할 수 있는 고유한 ID를 생성하는 리액트 훅이에요.
const id = useId();
Reference | 레퍼런스
useId()
고유 ID를 생성하려면 최상위 컴포넌트에서 useId
를 호출하세요.
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
}
더 많은 예시를 보려면 아래 예시들을 참고하세요.
Parameters | 매개변수(파라미터)
useId
는 어떤 매개변수도 받지 않아요.
Returns | 반환값
useId
는 해당 컴포넌트에서 특정한 useId
호출과 연관된 고유 ID 문자열을 반환해요.
Caveats | 주의사항
useId
는 훅이기 때문에 최상위 컴포넌트 또는 직접 만든 훅에서만 호출할 수 있어요. 반복문이나 조건문 안에서 호출할 수 없어요. 만약 반복문이나 조건문 안에서 호출해야한다면 새로운 컴포넌트로 추출하여 그 안으로 상태를 옮겨야해요.useId
는 리스트에서 키를 생성하는데 사용하면 안돼요. 키는 사용하는 데이터에서 생성되어야만 해요.
Usage | 용법
함정
useId는 리스트에서 키를 생성하는데 사용하면 안돼요. 키는 사용하는 데이터에서 생성되어야만 해요.
Generating unique IDs for accessibility attributes | 접근성 속성에 고유한 ID 생성하기
고유한 ID를 생성하려면 최상위 컴포넌트에서 useId
를 호출하세요.
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
}
생성된 ID는 다른 속성에 전달할 수 있어요.
<>
<input type="password" aria-describedby={passwordHintId} />
<p id={passwordHintId}>
</>
이 훅이 언제 유용한지 예시를 통해 확인해보아요.
aria-describedby
와 같은 HTML 접근성 속성은 두 개의 태그가 서로 관게되어있음을 정해줘요. 예를 들어, (input과 같은) 엘리먼트가 (paragraph와 같은) 다른 엘리먼트에 의하여 설명되도록 지정할 수 있어요.
보통의 HTML에서는 아래와 같이 작성하면 돼요.
<label>
비밀번호:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
비밀번호는 최소 18자 이상이어야 해요.
</p>
그러나 위와 같이 하드코딩된 ID는 리액트에서 좋은 습관이 아니에요. 컴포넌트는 페이지에서 한 번 이상 렌더링 되지만 ID는 고유해야만 해요! 하드코딩된 ID 대신 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
가 스크린에 여러번 보이더라도 생성된 ID는 충돌하지 않아요.
보조 기술을 통해 사용자 경험에서의 차이를 보고 싶다면 이 영상을 시청하세요.
함정
서버 렌더링에서 useId는 서버와 클라이언트 모두에게 동일한 컴포넌트 트리가 필요해요. 만약 서버와 클라이언트에서 렌더링한 트리가 서로 정확히 동일하지 않는다면 생성된 ID도 맞지 않을 거예요.
왜 useId가 카운터(counter)를 증가시키는 것보다 나을까요?
왜 useId
가 nextId++
과 같이 전역변수를 증가시키는 것보다 더 나은지 궁금할 거예요.
useId
의 가장 큰 이점은 리액트가 서버 렌더링과 함께 작동한다는 것을 보장한다는 점이에요. 서버 렌더링 동안 컴포넌트는 HTML 결과물을 생성해요. 그 후에, 클라이언트에서 하이드레이션(hydration)이 이벤트 핸들러를 생성된 HTML과 연결해요. 하이드레이션이 작동하려면 클라이언트 결과물은 서버 HTML과 일치해야만 해요.
클라이언트 컴포넌트가 하이드레이트된 순서는 서버 HTML이 생성된 순서와 일치하지 않을 수도 있기 때문에 카운터를 증가시키는 것을 보장하기는 어려워요. useId
를 호출함으로써 하이트레이션이 동작할 것임을 보장하고 결과물이 서버와 클라이언트 모두 일치한다는 것을 보장해요.
리액트 내부에서 useId
는 호출한 컴포넌트의 "부모 경로"에서 생성돼요. 그 이유는 만약 클라이언트나 서버 트리가 동일하다면 "부모 경로"는 렌더링 순서와 상관 없이 일치할 것이기 때문이에요.
Generating IDs for several related elements | 몇몇의 관련된 엘리먼트에 고유한 ID 생성하기
만약 연관된 여러 엘리먼트에 ID를 부여해야한다면, 공유된 접두사를 생성하기 위해 useId
를 호출하세요.
이는 고유한 ID가 필요한 모든 단일 엘리먼트를 위해 useId
를 호출하는 것을 피하게 해줘요.
Specifying a shared prefix for all generated IDs | 생성된 모든 Id에 공유된 접두사 지정하기
만약 여러개의 독립적인 리액트 앱을 한 페이지에 렌더링 해야 한다면, createRoot
나 hydrateRoot
에 옵션으로 identifierPrefix
를 전달하세요. 이는 useId
로 생성된 모든 생성자가 지정한 구분하는 접두사와 함께 실행되기 때문에 두 개의 다른 어플리케이션에서 생성된 ID가 절대 충돌하지 않는다는 것을 보장해요.
Using the same ID prefix on the client and the server | 서버와 클라이언트에 동일한 ID 접두사 사용하기
만약 여러개의 독립적인 리액트 앱을 한 페이지에 렌더링 해야 한다면 그리고 이 앱들 중 몇몇개는 서버에서 렌더링된다면 클라이언트 사이드에서 호출한 hydrateRoot
에 전달한 identifierPrefix
는 renderToPipeableStream
과 같이 server APIs에 전달한 identifierPrefix
와 같아요.
// 서버
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(
<App />,
{ identifierPrefix: 'react-app1' }
);
// 클라이언트
import { hydrateRoot } from 'react-dom/client';
const domNode = document.getElementById('root');
const root = hydrateRoot(
domNode,
reactNode,
{ identifierPrefix: 'react-app1' }
);
만약 한 페이지에 하나의 리액트 앱만 있다면 identifierPrefix
를 전달할 필요는 없어요.
'리액트 공식문서 | React Docs > Reference > react@18.2.0' 카테고리의 다른 글
[Hooks] useInsertionEffect | useInsertionEffect 훅 (0) | 2024.01.25 |
---|---|
[Hooks] useImperativeHandle | useImperativeHandle훅 (1) | 2024.01.21 |
[Hooks] useEffect | useEffect 훅 (0) | 2024.01.18 |
[Hooks] useDeferredValue | useDeferredValue 훅 (1) | 2024.01.14 |
[Hooks] useDebugValue | useDebugValue 훅 (0) | 2024.01.11 |