class가 자바스크립트 예약어이기 때문입니다. for도 htmlFor로 씁니다.
이 파트에서 다루는 내용
JSX는 자바스크립트 안에 쓰는 화면 표현입니다
JSX는 HTML처럼 보이지만 자바스크립트입니다. 빌드 과정에서 함수 호출로 변환되며, 그래서 자바스크립트 문법 규칙을 따릅니다.
HTML과 다른 지점이 몇 가지 있고, 처음에는 여기서 자주 막힙니다.
onclick이 아니라 onClick, tabindex가 아니라 tabIndex입니다.
중괄호 안에는 값을 만들어 내는 표현식만 들어갑니다. if 문이나 for 문은 쓸 수 없습니다.
반환값은 하나의 요소로 감싸야 합니다. 불필요한 div를 만들고 싶지 않으면 빈 태그(프래그먼트)를 씁니다.
function UserCard({ user }) {
const displayName = user.nickname ?? user.name;
return (
<>
<h3 className="card-title">{displayName}</h3>
<p>{user.email}</p>
{/* 주석은 이렇게 씁니다 */}
<label htmlFor="agree">동의</label>
<input id="agree" type="checkbox" defaultChecked={user.agreed} />
</>
);
}중괄호 안에서 계산을 길게 하기보다, 위쪽에서 변수로 만들어 두면 읽기 쉬워집니다.
props는 위에서 아래로만 흐릅니다
부모가 자식에게 데이터를 넘기는 통로가 props입니다. 방향은 한쪽입니다. 자식이 받은 props를 직접 바꿀 수는 없습니다.
자식에서 부모의 값을 바꿔야 한다면 값을 넘기는 것이 아니라 '바꾸는 함수'를 넘깁니다. 데이터는 내려가고 이벤트는 올라오는 구조입니다.
받은 props를 직접 수정하면 데이터 흐름이 깨져 원인 추적이 어려워집니다. 필요하면 새 값을 만들어 부모에게 알립니다.
여는 태그와 닫는 태그 사이의 내용은 children으로 전달됩니다. 레이아웃이나 카드처럼 감싸는 컴포넌트에 씁니다.
props가 지나치게 많아지면 컴포넌트가 너무 많은 일을 하고 있다는 뜻입니다. 나누는 것을 검토합니다.
function SearchBar({ keyword, onKeywordChange, onSubmit }) {
return (
<form onSubmit={onSubmit}>
<input
value={keyword}
onChange={(event) => onKeywordChange(event.target.value)}
/>
<button type="submit">검색</button>
</form>
);
}
// 상태는 부모가 가지고 있습니다
function OrderSearchPage() {
const [keyword, setKeyword] = useState("");
return (
<SearchBar
keyword={keyword}
onKeywordChange={setKeyword}
onSubmit={handleSubmit}
/>
);
}SearchBar는 상태를 갖지 않고 받은 값만 표시합니다. 이런 컴포넌트는 재사용과 테스트가 쉽습니다.
조건과 목록은 자바스크립트로 처리합니다
JSX에는 전용 분기 문법이 없습니다. 자바스크립트 표현식을 그대로 씁니다.
function OrderPanel({ order, isLoading }) {
// 1) 이른 반환: 조건이 명확할 때 가장 읽기 쉽습니다
if (isLoading) {
return <Spinner />;
}
return (
<div>
{/* 2) 삼항 연산자: 둘 중 하나를 고를 때 */}
{order.isPaid ? <PaidBadge /> : <PendingBadge />}
{/* 3) && 연산자: 조건이 참일 때만 표시 */}
{order.memo && <p className="memo">{order.memo}</p>}
</div>
);
}&& 를 쓸 때 왼쪽이 숫자 0이면 0이 화면에 그대로 출력됩니다. 개수를 조건으로 쓸 때는 count > 0 처럼 명확한 비교식을 씁니다.
function OrderList({ orders }) {
if (orders.length === 0) {
return <p className="empty">조회된 주문이 없습니다.</p>;
}
return (
<ul>
{orders.map((order) => (
<li key={order.id}>
<OrderRow order={order} />
</li>
))}
</ul>
);
}빈 목록 처리를 먼저 해 두면 화면이 비어 보이는 상황을 막을 수 있습니다. 실무에서 자주 빠뜨리는 부분입니다.
key를 배열 인덱스로 쓰면 안 되는 이유
React는 목록이 바뀌었을 때 어떤 항목이 그대로이고 어떤 항목이 추가·삭제됐는지 key로 판단합니다.
인덱스를 key로 쓰면 항목이 이동하거나 중간이 삭제될 때 판단이 어긋납니다. 화면에 표시되는 데이터와 입력 상태가 서로 어긋나는 문제가 생깁니다.
- 목록 중간을 삭제하면 엉뚱한 행의 입력값이 남습니다
- 정렬을 바꾸면 체크박스 선택이 다른 항목으로 옮겨집니다
- 애니메이션이 어색하게 동작합니다
- 데이터가 가진 고유 식별자를 씁니다
- 목록 안에서만 유일하면 됩니다
- 렌더링할 때마다 새로 만들지 않습니다
목록이 절대 바뀌지 않고, 순서 변경과 추가·삭제가 없으며, 항목이 자체 상태를 갖지 않는 경우입니다. 조건이 하나라도 어긋나면 쓰지 않습니다.
// 위험: 순서가 바뀌거나 중간이 삭제되면 어긋납니다
{items.map((item, index) => <Row key={index} item={item} />)}
// 위험: 렌더링마다 값이 바뀌어 매번 새로 만들어집니다
{items.map((item) => <Row key={Math.random()} item={item} />)}
// 권장: 데이터의 고유 식별자
{items.map((item) => <Row key={item.id} item={item} />)}key는 화면을 그리기 위한 값이 아니라 React가 항목을 식별하기 위한 값입니다. 사용자에게 보이지 않지만 목록 동작의 정확성을 좌우합니다.
버전별 참고
본문은 React 19 기준입니다. 18과 달라지는 부분은 아래에 정리합니다.
- React 17부터 JSX를 쓰기 위해 파일 상단에 React를 import 하지 않아도 됩니다. 오래된 예제에 남아 있는 import 문은 지금 기준으로는 불필요합니다.
- React 19에서는 ref를 일반 prop처럼 전달할 수 있게 되어, 예전에 쓰던 별도 전달 방식이 필요 없어졌습니다.