React ์ํ๊ด๋ฆฌ
React๋ฅผ ์ฒ์ ๋ฐฐ์ธ ๋ ์ํ ๊ด๋ฆฌ๋ ๋จ์ํ๋ค.
useState
๊ทธ๋ฐ๋ฐ ํ๋ก์ ํธ๊ฐ ์กฐ๊ธ๋ง ์ปค์ง๋ฉด ์ด๋ฐ ์ง๋ฌธ์ด ๋์ค๊ธฐ ์์ํ๋ค.
- ์ด ์ํ๋ฅผ ์ด๋์ ๋ฌ์ผ ํ์ง?
- props๋ก ๋ด๋ฆฌ๊ธฐ์ ๋๋ฌด ๋ฉ๋ค
- ์ ์ญ ์ํ๊ฐ ํ์ํ๊ฐ?
- ์ด๊ฑด ์๋ฒ ๋ฐ์ดํฐ ์๋๊ฐ?
React ์ํ๊ด๋ฆฌ๋ “์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐ๋๋”์ ๋ฌธ์ ๊ฐ ์๋๋ผ
์ด ์ํ๊ฐ ์ด๋๊น์ง ์ํฅ์ ๋ฏธ์ณ์ผ ํ๋๋์ ๋ฌธ์ ๋ค.
1. ์ปดํฌ๋ํธ ๋ก์ปฌ ์ํ: ๊ฐ์ฅ ๋จผ์ ๊ณ ๋ คํด์ผ ํ ์ ํ์ง
React์ ๊ธฐ๋ณธ ์ํ๊ด๋ฆฌ ๋ฉ์ปค๋์ฆ์ ์ฌ์ ํ ์ ํจํ๋ค.
- useState
- useReducer
ํ ๊ธ, ์
๋ ฅ๊ฐ, ๋ชจ๋ฌ ์ด๋ฆผ ์ฌ๋ถ์ฒ๋ผ
์ปดํฌ๋ํธ ๋ด๋ถ์์๋ง ์๋ฏธ๊ฐ ์๋ ์ํ๋ผ๋ฉด ์ด ์ด์์ด ํ์ ์๋ค.
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
}
์ธ์ ์ฐ๋ฉด ์ถฉ๋ถํ๊ฐ
- ์ํ์ ์ํฅ ๋ฒ์๊ฐ ํด๋น ์ปดํฌ๋ํธ์๋ง ์์ ๋
- props๋ง์ผ๋ก ๊ตฌ์กฐ๊ฐ ๋ช ํํ๊ฒ ์ ์ง๋ ๋
๋ง์ ์ํ ๋ฌธ์ ๋ ์ฌ์ค “์ ์ญ์ผ๋ก ๋ง๋ค์ง ์์๋ ๋๋๋ฐ ์ ์ญ์ผ๋ก ๋ง๋ ๊ฒ”์์ ์์๋๋ค.
2. URL / Router ์ํ: ๊ณต์ ๋์ด์ผ ํ๋ ์ํ์ ์์ฐ์ค๋ฌ์ด ์์น
๊ฒ์ ์กฐ๊ฑด, ํํฐ, ํ์ด์ง ๋ฒํธ ๊ฐ์ ์ํ๋
์ฌ์ค UI ์ํ์ด๊ธฐ ์ด์ ์ ๋ค๋น๊ฒ์ด์
์ํ๋ค.
์ด๊ฑธ ์ปดํฌ๋ํธ state๋ก๋ง ๋ค๊ณ ์์ผ๋ฉด ๋ฌธ์ ๊ฐ ์๊ธด๋ค.
- ์๋ก๊ณ ์นจํ๋ฉด ์ด๊ธฐํ๋จ
- ๋ค๋ก๊ฐ๊ธฐ ๋์์ด ์ด์ํจ
- ๋งํฌ ๊ณต์ ๊ฐ ๋ถ๊ฐ๋ฅํจ
๊ทธ๋์ ์ด ์ํ๋ค์ URL๋ก ์ฌ๋ผ๊ฐ๋ค.
function ProductList() {
const [params, setParams] = useSearchParams();
const category = params.get('category') ?? 'all';
return (
<select
value={category}
onChange={e => setParams({ category: e.target.value })}
>
<option value="all">์ ์ฒด</option>
<option value="shoes">์ ๋ฐ</option>
<option value="bag">๊ฐ๋ฐฉ</option>
</select>
);
}
์ธ์ URL ์ํ๊ฐ ๋ง๋๊ฐ
- ์๋ก๊ณ ์นจ ํ์๋ ์ ์ง๋์ด์ผ ํ๋ ์ํ
- ๋ค๋ก๊ฐ๊ธฐ / ๋ถ๋งํฌ๊ฐ ์๋ฏธ๋ฅผ ๊ฐ์ ธ์ผ ํ๋ ์ํ
- “ํ์ฌ ํ์ด์ง์ ์กฐ๊ฑด”์ ์ค๋ช ํ๋ ์ํ
URL์ ์๊ฐ๋ณด๋ค ๊ฐ๋ ฅํ ์ํ ์ ์ฅ์๋ค.
3. ์๋ฒ ๋ฐ์ดํฐ ์ํ: ์ด๊ฑด ํด๋ผ์ด์ธํธ ์ํ๊ฐ ์๋๋ค
useEffect + fetch ์กฐํฉ์ ์ฒ์์ ๊ฐ๋จํด ๋ณด์ธ๋ค.
ํ์ง๋ง ๊ณง ์ด๋ฐ ๋ฌธ์ ๋ฅผ ๋ง์ฃผํ๋ค.
- ๋ก๋ฉ ์ํ
- ์๋ฌ ์ํ
- ์ฌ๋ฌ ์ปดํฌ๋ํธ ๊ฐ ๋ฐ์ดํฐ ๊ณต์
- ์บ์, ์ฌ์์ฒญ, ๋๊ธฐํ
์ด๊ฑธ ๋งค๋ฒ ์ง์ ๊ตฌํํ๋ ๊ฑด ๋นํจ์จ์ ์ด๋ค.
๊ทธ๋์ ์๋ฒ ๋ฐ์ดํฐ ์ ์ฉ ์ํ๊ด๋ฆฌ๊ฐ ๋ฑ์ฅํ๋ค.
function Todos({ filter }) {
const { data, isLoading, error } = useQuery({
queryKey: ['todos', filter],
queryFn: () => fetch(`/api/todos?f=${filter}`).then(r => r.json()),
staleTime: 60_000,
});
if (isLoading) return <p>๋ก๋ฉ์ค...</p>;
if (error) return <p>์๋ฌ!</p>;
return <ul>{data.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
}
์ค์ํ ๊ด์
์๋ฒ ๋ฐ์ดํฐ๋ “์ํ”๋ผ๊ธฐ๋ณด๋ค “์บ์๋ ๊ฒฐ๊ณผ”์ ๊ฐ๊น๋ค.
๊ทธ๋์ ์ ์ญ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ด๋ฆฌํ๋ ค ํ๋ฉด ์คํ๋ ค ๋ณต์กํด์ง๋ค.
4. ์ ์ญ ํด๋ผ์ด์ธํธ ์ํ: ์ง์ง ์ ์ญ๋ง ๋จ๊ฒจ๋ผ
์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ๋์์ ์ ๊ทผํด์ผ ํ๋ ์ํ๊ฐ ์๋ค.
- ๋ก๊ทธ์ธ ์ฌ์ฉ์ ์ ๋ณด
- ํ ๋ง
- ๊ธ๋ก๋ฒ ์๋ฆผ
์ด๊ฑธ props๋ก๋ง ์ ๋ฌํ๋ ค ํ๋ฉด
๊ณง props drilling ๋ฌธ์ ๊ฐ ํฐ์ง๋ค.
const useUI = create((set) => ({
theme: 'light',
toggle: () => set(s => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
}));
์ธ์ ์ ์ญ ์ํ๊ฐ ํ์ํ๊ฐ
- ํ์ด์ง๋ฅผ ๋์ด ๊ณต์ ๋๋ ์ํ
- UI ์ ๋ฐ์ ์ํฅ์ ์ฃผ๋ ์ค์ ๊ฐ
- ์๋ฒ ๋ฐ์ดํฐ๊ฐ ์๋ ์์ ํด๋ผ์ด์ธํธ ์ํ
์ฌ๊ธฐ์ ์ค์ํ ๊ฑด ๊ฐ์๋ค.
์ ์ญ ์ํ๋ ๋ง์์ง์๋ก ๊ด๋ฆฌ ๋น์ฉ์ด ๊ธ๊ฒฉํ ์ฆ๊ฐํ๋ค.
5. ์ํ ๋จธ์ : ํ๋ฆ์ด ๋ณต์กํด์ง๋ ์๊ฐ ๋ฑ์ฅํ๋ค
๋ก๊ทธ์ธ, ๊ฒฐ์ , ์ ๋ก๋ ๊ฐ์ ๋ก์ง์ ๋ ์ฌ๋ ค๋ณด์.
- ์์ฒญ
- ์ฑ๊ณต
- ์คํจ
- ์ฌ์๋
- ์ทจ์
์ด๊ฑธ boolean state ๋ช ๊ฐ๋ก ๊ด๋ฆฌํ๊ธฐ ์์ํ๋ฉด
๊ธ๋ฐฉ ์ํ ์กฐํฉ์ด ํญ๋ฐํ๋ค.
idle → loading → success
↓
error → retry
์ํ ๋จธ์ ์ ์ด ํ๋ฆ์ ๋ช ์์ ์ผ๋ก ๊ณ ์ ํ๋ค.
export const loginMachine = createMachine({
initial: 'idle',
states: {
idle: { on: { SUBMIT: 'loading' } },
loading: {
invoke: {
src: fakeLogin,
onDone: 'success',
onError: 'error',
},
},
error: { on: { RETRY: 'loading' } },
success: { type: 'final' },
},
});
์ธ์ ๊ณ ๋ คํ ๋งํ๊ฐ
- ์ํ ์ ์ด๊ฐ ๋ช ํํ ๋จ๊ณํ ๋ก์ง
- “์ด ์ํ์์ ์ด ํ๋์ ๋ถ๊ฐ๋ฅํด์ผ ํ๋ค”๊ฐ ์ค์ํ ๊ฒฝ์ฐ
๋ชจ๋ ๊ณณ์ ์ธ ํ์๋ ์์ง๋ง,
ํ์ํ ๊ณณ์์๋ ๊ต์ฅํ ๊ฐ๋ ฅํ๋ค.
6. ์๋ฒ ์ปดํฌ๋ํธ / ์๋ฒ ์ก์ : ์ํ๋ฅผ ์์ ๋ ์ ํ
Next.js App Router ์ดํ ๋ฑ์ฅํ ๋ฐฉํฅ์ฑ์ ๋ถ๋ช ํ๋ค.
“ํด๋ผ์ด์ธํธ์์ ์ํ๋ฅผ ๊ด๋ฆฌํ์ง ๋ง์.”
์๋ฒ์์ ๋ฐ์ดํฐ ๋ก์ง์ ์คํํ๊ณ ,
ํด๋ผ์ด์ธํธ๋ ๊ฒฐ๊ณผ๋ง ๋ฐ๋๋ค.
'use server';
export async function addTodo(formData) {
const title = formData.get('title');
await db.todo.create({ data: { title } });
}
์ธ์ ์ข์ ์ ํ์ธ๊ฐ
- DB ์ ๊ทผ, ์ธ์ฆ, ๋ณด์์ด ์ค์ํ ๋ก์ง
- ์๋ฒ๊ฐ ๋ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌํ ์ ์๋ ์์
- ํด๋ผ์ด์ธํธ ์ํ ๋ณต์ก๋๋ฅผ ์ค์ด๊ณ ์ถ์ ๋
์ด๊ฑด “๋ ๋ค๋ฅธ ์ํ๊ด๋ฆฌ”๋ผ๊ธฐ๋ณด๋ค
์ํ ์์ฒด๋ฅผ ์ค์ด๋ ์ ๊ทผ์ ๊ฐ๊น๋ค.
7. ํผ ์ํ: ์ผ๋ฐ ์ํ๊ด๋ฆฌ๋ก๋ ๋ฒ๊ฒ๋ค
ํผ์ ํน์ดํ๋ค.
- ์ ๋ ฅ๊ฐ
- ์๋ฌ
- touched
- submitting
- validation
์ด๊ฑธ useState๋ก ๊ด๋ฆฌํ๋ฉด ์ฝ๋๊ฐ ๊ธ๊ฒฉํ ์ง์ ๋ถํด์ง๋ค.
const { register, handleSubmit, formState } = useForm();
React Hook Form ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋
ํผ ์ํ๋ฅผ ์ฑ๋ฅ๊น์ง ๊ณ ๋ คํด์ ๋ถ๋ฆฌํด์ค๋ค.
์ธ์ ํ์ํด์ง๋๊ฐ
- ํ๋๊ฐ ๋ง์ ํผ
- ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ
- ๋ ๋๋ง ์ฑ๋ฅ์ด ์ค์ํ ๊ฒฝ์ฐ
์ ๋ฆฌ: ์ํ๊ด๋ฆฌ๋ ๋๊ตฌ ์ ํ ๋ฌธ์ ๊ฐ ์๋๋ค
React ์ํ๊ด๋ฆฌ๋ ์ด๋ ๊ฒ ์ ๋ฆฌํ ์ ์๋ค.
- ๋ฒ์๊ฐ ์์ผ๋ฉด → ์ปดํฌ๋ํธ ๋ก์ปฌ
- ๊ณต์ ๋์ด์ผ ํ๋ฉด → URL ๋๋ ์ ์ญ
- ์๋ฒ์์ ์์ผ๋ฉด → ์๋ฒ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ํ๋ฆ์ด ๋ณต์กํ๋ฉด → ์ํ ๋จธ์
- ์์จ ์ ์์ผ๋ฉด → ์๋ฒ๋ก ์ด๋
๋ชจ๋ ๊ฑธ ํ๋๋ก ํตํฉํ๋ ค๋ ์๊ฐ,
์ํ๊ด๋ฆฌ๋ ๋ณต์กํด์ง๋ค.
์ ์ค๊ณ๋ React ์ฑ์
์ํ๊ฐ ๋ง์ ์ฑ์ด ์๋๋ผ, ์ํ์ ์๋ฆฌ๊ฐ ๋ช
ํํ ์ฑ์ด๋ค.
'๐ ํ๋ก ํธ์๋(FE)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ํ๋ก ํธ์๋ ํ ์คํธ ๋๊ตฌ ( MSW, Vitest, React Testing Library ) (0) | 2026.01.21 |
|---|---|
| FE ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ(FE ์ํคํ ์ณ) (0) | 2026.01.20 |
| React ์ฑ๋ฅ ์ต์ ํ (0) | 2026.01.20 |
| CSR / SSR / SSG / ISR ๋ ๋๋ง ๋ฐฉ์ ๋น๊ต (1) | 2026.01.17 |
| Next.js 16 + Turbopack + pnpm ๋ชจ๋ ธ๋ ํฌ: Docker ๋น๋ ์์ ์ ๋ณต (0) | 2026.01.17 |