useId
๋ ์ ๊ทผ์ฑ ์ดํธ๋ฆฌ๋ทฐํธ์ ์ ๋ฌํ ์ ์๋ ๊ณ ์ ID๋ฅผ ์์ฑํ๊ธฐ ์ํ React Hook์
๋๋ค.
const id = useId()
๋ ํผ๋ฐ์ค
useId()
useId
๋ฅผ ์ปดํฌ๋ํธ์ ์ต์์์์ ํธ์ถํ์ฌ ๊ณ ์ ID๋ฅผ ์์ฑํฉ๋๋ค.
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
// ...
์๋์์ ๋ ๋ง์ ์์๋ฅผ ํ์ธํ์ธ์.
๋งค๊ฐ๋ณ์
useId
๋ ์ด๋ค ๋งค๊ฐ๋ณ์๋ ๋ฐ์ง ์์ต๋๋ค.
๋ฐํ๊ฐ
useId
๋ฅผ ํธ์ถํ ํน์ ์ปดํฌ๋ํธ์ ํน์ useId
์ ๊ด๋ จ๋ ๊ณ ์ ID ๋ฌธ์์ด์ ๋ฐํํฉ๋๋ค.
์ฃผ์ ์ฌํญ
-
useId
๋ Hook์ด๋ฏ๋ก ์ปดํฌ๋ํธ์ ์ต์์ ๋๋ ์ปค์คํ Hook์์๋ง ํธ์ถํ ์ ์์ต๋๋ค. ๋ฐ๋ณต๋ฌธ์ด๋ ์กฐ๊ฑด๋ฌธ์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ํ ๊ฒฝ์ฐ ์๋ก์ด ์ปดํฌ๋ํธ๋ฅผ ์ถ์ถํ๊ณ ํด๋น ์ปดํฌ๋ํธ๋ก state๋ฅผ ์ด๋ํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค. -
useId
๋ฅผ ๋ฆฌ์คํธ์ key๋ฅผ ์์ฑํ๊ธฐ ์ํด ์ฌ์ฉํ๋ฉด ์ ๋ฉ๋๋ค. Key๋ ๋ฐ์ดํฐ๋ก๋ถํฐ ์์ฑํด์ผ ํฉ๋๋ค.
์ฌ์ฉ๋ฒ
์ ๊ทผ์ฑ ์ดํธ๋ฆฌ๋ทฐํธ๋ฅผ ์ํ ๊ณ ์ 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>
Password:
<input
type="password"
aria-describedby="password-hint"
/>
</label>
<p id="password-hint">
The password should contain at least 18 characters
</p>
React์์ ID๋ฅผ ์ง์ ์ฝ๋์ ์
๋ ฅํ๋ ๊ฒ์ ์ข์ ์ฌ๋ก๊ฐ ์๋๋๋ค. ํ์ด์ง์์ ์ปดํฌ๋ํธ๋ ๋ช ๋ฒ์ด๊ณ ๋ ๋๋ง ๋ ์ ์์ง๋ง ID๋ ๊ณ ์ ํด์ผ ํฉ๋๋ค. ID๋ฅผ ์ง์ ์
๋ ฅํ๋ ๋์ useId
๋ฅผ ํ์ฉํด์ ๊ณ ์ ํ ID๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
import { useId } from 'react';
function PasswordField() {
const passwordHintId = useId();
return (
<>
<label>
Password:
<input
type="password"
aria-describedby={passwordHintId}
/>
</label>
<p id={passwordHintId}>
The password should contain at least 18 characters
</p>
</>
);
}
์ด์ PasswordField
๊ฐ ํ๋ฉด์ ์ฌ๋ฌ ๋ฒ ๋ํ๋๋ ์์ฑ๋ ID๋ ์ถฉ๋ํ์ง ์์ต๋๋ค.
import { useId } from 'react'; function PasswordField() { const passwordHintId = useId(); return ( <> <label> Password: <input type="password" aria-describedby={passwordHintId} /> </label> <p id={passwordHintId}> The password should contain at least 18 characters </p> </> ); } export default function App() { return ( <> <h2>Choose password</h2> <PasswordField /> <h2>Confirm password</h2> <PasswordField /> </> ); }
์์์ ํตํด ๋ณด์กฐ ๊ธฐ์ ์ ํ์ฉํ์ ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ฐจ์ด์ ์ ํ์ธํ ์ ์์ต๋๋ค.
Deep Dive
useId
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด nextId++
์ฒ๋ผ ์ ์ญ ๋ณ์๋ฅผ ์ฆ๊ฐํ๋ ๊ฒ๋ณด๋ค ๋์ ์ด์ ์ ๋ํด ๊ถ๊ธํ ์ ์์ต๋๋ค.
useId
์ ์ฃผ์ ์ด์ ์ React๊ฐ ์๋ฒ ๋ ๋๋ง๊ณผ ํจ๊ป ์๋ํ๋๋ก ๋ณด์ฅํ๋ค๋ ๊ฒ์
๋๋ค. ์๋ฒ ๋ ๋๋ง์ ํ๋ ๋์ ์ปดํฌ๋ํธ๋ HTML ๊ฒฐ๊ณผ๋ฌผ์ ์์ฑํฉ๋๋ค. ์ดํ, ํด๋ผ์ด์ธํธ์์ hydration์ด HTML ๊ฒฐ๊ณผ๋ฌผ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค. hydration์ด ๋์ํ๋ ค๋ฉด ํด๋ผ์ด์ธํธ์ ์ถ๋ ฅ์ด ์๋ฒ HTML๊ณผ ์ผ์นํด์ผ ํฉ๋๋ค.
ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ hydrated ์์๊ฐ ์๋ฒ HTML์ด ์์ฑ๋ ์์์ ์ผ์นํ์ง ์์ ์ ์๊ธฐ ๋๋ฌธ์ ์นด์ดํฐ ์ฆ๊ฐ๋ก ์ด๋ฅผ ๋ณด์ฅํ๊ธฐ๋ ๋งค์ฐ ์ด๋ ต์ต๋๋ค. useId
๋ฅผ ์ฌ์ฉํ๋ฉด hydration์ด ๋์ํ๊ณ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ์ ์ถ๋ ฅ์ด ์ผ์นํ๋ ๊ฒ์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
React์์ useId
๋ ํธ์ถํ ์ปดํฌ๋ํธ์ โ๋ถ๋ชจ ๊ฒฝ๋กโ์์ ์์ฑ๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ์ ์๋ฒ ํธ๋ฆฌ๊ฐ ๋์ผํ ๊ฒฝ์ฐ ๋ ๋๋ง ์์์ ๊ด๊ณ์์ด โ๋ถ๋ชจ ๊ฒฝ๋กโ๊ฐ ์ผ์นํ๋ ์ด์ ์
๋๋ค.
์ฌ๋ฌ ๊ฐ์ ์ฐ๊ด๋ ์๋ฆฌ๋จผํธ์ ID ์์ฑํ๊ธฐ
์ฌ๋ฌ ๊ฐ์ ์ฐ๊ด๋ ์๋ฆฌ๋จผํธ์ ID๋ฅผ ์ ๋ฌํ๋ ๊ณผ์ ์ด ํ์ํ ๋ useId
๋ฅผ ์ฌ์ฉํด์ ๊ณต์ ์ ๋์ฌ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
import { useId } from 'react'; export default function Form() { const id = useId(); return ( <form> <label htmlFor={id + '-firstName'}>First Name:</label> <input id={id + '-firstName'} type="text" /> <hr /> <label htmlFor={id + '-lastName'}>Last Name:</label> <input id={id + '-lastName'} type="text" /> </form> ); }
useId
๋ฅผ ๊ณ ์ ํ ID๊ฐ ํ์ํ ๋ชจ๋ ์๋ฆฌ๋จผํธ์์ ์คํํ๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์์ฑ๋ ๋ชจ๋ ID์ ๋ํด ๊ณต์ ์ ๋์ฌ ์ง์ ํ๊ธฐ
์ฌ๋ฌ ๊ฐ์ ๋
๋ฆฝ๋ React ์ ํ๋ฆฌ์ผ์ด์
์ ํ๋์ ํ์ด์ง์์ ๋ ๋๋งํ๋ค๋ฉด identifierPrefix
๋ฅผ createRoot
๋๋ hydrateRoot
ํธ์ถ์ ๋ํ ์ต์
์ผ๋ก ์ ๋ฌํฉ๋๋ค. useId
๋ก ์์ฑ๋ ๋ชจ๋ ์๋ณ์๊ฐ ๋ณ๊ฐ์ ์ ๋์ฌ๋ก ์์ํ๋ฏ๋ก ์๋ก ๋ค๋ฅธ ๋ ๊ฐ์ ์ฑ์์ ์์ฑ๋ ID๊ฐ ์ถฉ๋ํ์ง ์๋ ๊ฒ์ ๋ณด์ฅํฉ๋๋ค.
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 />);
ํด๋ผ์ด์ธํธ์ ์๋ฒ์์ ๋์ผํ ID ์ ๋์ฌ ์ฌ์ฉํ๊ธฐ
๋์ผํ ํ์ด์ง์์ ์ฌ๋ฌ ๋
๋ฆฝ์ ์ธ React ์ฑ์ ๋ ๋๋งํ๋ ๊ฒฝ์ฐ, ์ด๋ฌํ ์ฑ ์ค ์ผ๋ถ๊ฐ ์๋ฒ์์ ๋ ๋๋ง๋๋ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ ์ธก์์ hydrateRoot
ํธ์ถ์ ์ ๋ฌํ๋ identifierPrefix๊ฐ
renderToPipeableStream
์ ๊ฐ์ ์๋ฒ API์ ์ ๋ฌํ๋ identifierPrefix
์ ๋์ผํ์ง ํ์ธํด์ผ ํฉ๋๋ค.
// 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.