출시·고도화 중
React 안내서 · 3/6
React 앱은 컴포넌트를 조합해 만듭니다. 이 장에서는 컴포넌트와 JSX, props와 상태, 자주 쓰는 훅, 목록 렌더링과 key, 그리고 컴포넌트를 조합하는 방법을 차례로 살펴봅니다.
컴포넌트는 JSX를 반환하는 JavaScript 함수입니다. JSX는 HTML처럼 보이지만 JavaScript로 변환되는 문법이라 몇 가지 규칙이 있습니다.
<>...</>(Fragment)를 씁니다.class 대신 className, onclick 대신 onClick처럼 속성은 카멜 표기법을 씁니다.<img />, <input />처럼 모든 태그를 닫아야 합니다.{} 안에는 변수, 함수 호출 같은 JavaScript 표현식을 넣습니다.const user = { name: "하늘", avatarUrl: "/avatars/sky.png" };
export function Profile() {
return (
<>
<h2 className="title">{user.name}</h2>
<img src={user.avatarUrl} alt={`${user.name}의 사진`} width={64} />
</>
);
}props는 부모 컴포넌트가 자식에게 넘기는 읽기 전용 입력값입니다. TypeScript에서는 props의 타입을 정해 두면 잘못된 사용을 컴파일 단계에서 막을 수 있습니다. 특별한 prop인 children에는 여는 태그와 닫는 태그 사이의 내용이 들어옵니다.
import type { ReactNode } from "react";
type CardProps = {
title: string;
variant?: "default" | "warning";
children: ReactNode;
};
export function Card({ title, variant = "default", children }: CardProps) {
return (
<section className={`card card-${variant}`}>
<h3>{title}</h3>
{children}
</section>
);
}
// 사용: <Card title="알림" variant="warning"><p>저장되지 않았습니다.</p></Card>상태(state)는 컴포넌트가 기억하는 값입니다. useState는 현재 값과 값을 바꾸는 함수를 돌려주며, 설정 함수를 호출하면 React가 컴포넌트를 다시 렌더링합니다. 상태는 직접 고치지 말고 항상 새 값으로 바꿔야 합니다. 객체나 배열은 펼침 연산자로 복사본을 만들어 넘깁니다.
import { useState } from "react";
export function ItemAdder() {
const [count, setCount] = useState(0);
const [items, setItems] = useState<string[]>([]);
function addItem() {
setItems([...items, `할 일 ${items.length + 1}`]); // 새 배열
setCount((c) => c + 1); // 이전 값을 바탕으로 계산할 때는 함수형 업데이트
}
return <button onClick={addItem}>추가 ({count})</button>;
}조건에 따라 다른 화면을 보여 줄 때는 if, 삼항 연산자, &&를 씁니다. 배열은 map으로 JSX 목록을 만들고, 각 항목에 형제들 사이에서 고유하고 변하지 않는 key를 줍니다. React는 key로 항목을 구분하므로, 순서가 바뀔 수 있는 목록에서 배열 인덱스를 key로 쓰면 상태가 엉뚱한 항목에 붙을 수 있습니다.
type Todo = { id: string; text: string; done: boolean };
export function TodoList({ todos }: { todos: Todo[] }) {
if (todos.length === 0) return <p>할 일이 없습니다.</p>;
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text} {todo.done && "✓"}
</li>
))}
</ul>
);
}useEffect: 렌더링이 끝난 뒤 외부 시스템(타이머, 브라우저 API, 구독)과 동기화합니다. 의존성 배열의 값이 바뀔 때마다 다시 실행되고, 반환한 정리 함수는 다음 실행 전과 컴포넌트가 사라질 때 호출됩니다.useMemo: 비용이 큰 계산 결과를 의존성이 바뀔 때까지 재사용합니다.useRef: 다시 렌더링을 일으키지 않는 값을 보관하거나 DOM 요소에 접근합니다.import { useEffect, useMemo, useRef, useState } from "react";
export function SearchBox({ words }: { words: string[] }) {
const [query, setQuery] = useState("");
const [seconds, setSeconds] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // 정리
}, []);
const matches = useMemo(
() => words.filter((w) => w.includes(query)),
[words, query],
);
return (
<div>
<input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} />
<p>{matches.length}개 일치 · {seconds}초 경과</p>
</div>
);
}props나 상태에서 계산할 수 있는 값은 Effect가 아니라 렌더링 중에 바로 계산합니다. 훅은 컴포넌트나 사용자 정의 훅의 최상위에서만, 조건문이나 반복문 밖에서 호출해야 합니다(훅의 규칙).
React는 상속 대신 조합으로 컴포넌트를 재사용합니다. children이나 JSX를 받는 prop으로 틀을 만들고 내용은 바깥에서 채웁니다. 반복되는 상태 로직은 use로 시작하는 사용자 정의 훅으로 뽑아냅니다.
import { useState, type ReactNode } from "react";
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
return [on, () => setOn((v) => !v)] as const;
}
export function Panel({ header, children }: { header: ReactNode; children: ReactNode }) {
const [open, toggle] = useToggle(true);
return (
<div className="panel">
<button onClick={toggle}>{header}</button>
{open && children}
</div>
);
}useState로 만들고 항상 새 값으로 바꿉니다.key를 줍니다.useEffect는 외부 시스템과의 동기화에, useMemo는 비싼 계산에, useRef는 DOM 접근과 렌더링과 무관한 값에 씁니다.children과 사용자 정의 훅으로 조합합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.