조건부 렌더링 && 함정 리스트 key index 주의 📝 문제풀이
◀ 이전 📋 목차 CH 07 ▶
🔀 CHAPTER 06 · 중급

조건에 따라 보여주고, 목록을 그리기

화면은 항상 똑같지 않아요. 로그인했으면 이걸, 아니면 저걸 보여주고, 배열 데이터를 목록으로 쭉 그려야 하죠. 이번 장에서 조건부 렌더링리스트 렌더링(map · key)을 확실히 잡아봐요.

🎯 이 장을 끝내면
🔀
조건부 렌더링
조건에 따라 다른 걸 보여주기
"if"를 JSX 안에서 쓰는 여러 방법을 배워요.

조건부 렌더링(Conditional Rendering)어떤 조건이냐에 따라 화면에 다른 걸 보여주는 것이에요. 예를 들어 로그인 상태면 "로그아웃 버튼"을, 아니면 "로그인 버튼"을 보여주는 식이죠. JSX 안에서는 if 문을 직접 쓸 수 없어서, 주로 삼항 연산자논리 &&를 써요.

🔀
삼항 ? :
둘 중 하나를 보여줄 때
논리 &&
있을 때만 보여줄 때
🗂️
변수 · early return
복잡할 때 미리 담거나 먼저 반환
🧩 헷갈리지 않게 — 이 중 뭐가 "자바스크립트"고 뭐가 "리액트"예요? 결론부터요. 이 장의 판단 로직(삼항·&&·map…)은 전부 순수 자바스크립트예요. 리액트에서만 통하는 특별한 문법이 아니라 어디서든 쓰는 JS 기술이죠. 리액트가 더하는 건 딱 몇 가지 "규칙"뿐이에요.
🟦 순수 자바스크립트 (어디서나 씀)
· 삼항 조건 ? A : B
· 논리 && / ||
· 배열 .map() · .filter()
· 변수에 담기 · early return
JS 지식이라 바닐라JS·Node에서도 그대로 써요.
🟪 리액트만의 규칙 (여기서만)
· JSX { } 안에 넣어야 화면에 반영
· false·null·undefined는 안 그림, 그런데 0그림
· 목록엔 key를 붙여야 함
리액트 지식이에요.
👉 실무에서 "이거 JS야 리액트야?" 싶으면: 값을 계산·판단하는 부분이면 JS, "화면에 어떻게 반영되나"는 리액트라고 보면 대체로 맞아요.
① 삼항 연산자 조건 ? A : B 🟦 순수 JS 둘 중 하나를 보여줄 때 딱이에요. "조건이 참이면 A, 아니면 B."
function Greeting({ isLogin }) {
  return (
    <div>
      {isLogin ? <p>환영합니다!</p> : <p>로그인이 필요해요.</p>}
    </div>
  );
}
② 논리 && 조건 && A 🟦 순수 JS 있을 때만 보여주고, 아니면 아무것도 안 보여줄 때 써요. 조건이 참이면 A가 렌더링되고, 거짓이면 아무것도 안 나와요("있으면 보여주고 없으면 말고").
function Inbox({ count }) {
  return (
    <div>
      {count > 0 && <p>새 메시지 {count}개</p>}
    </div>
  );
}
③ 변수에 담기 · ④ early return 🟦 순수 JS 조건이 복잡하면 미리 변수에 JSX를 담아 두고 {content}처럼 꽂거나, 컴포넌트 맨 위에서 early return(먼저 반환)으로 처리하면 깔끔해요.
function Profile({ user }) {
  // early return: user가 없으면 여기서 끝
  if (!user) return <p>로그인이 필요해요.</p>;

  // 변수에 담기
  const badge = user.isAdmin ? <span>👑 관리자</span> : null;

  return (
    <div>
      <h2>{user.name}</h2>
      {badge}
    </div>
  );
}
🧭
고르는 기준. 둘 중 하나면 삼항, 있을 때만이면 &&, 복잡하면 변수/early return. 상황에 맞게 골라 쓰면 돼요.
⚠️
&& 함정
숫자 0이 화면에 그대로 찍히는 함정
&&를 쓸 때 가장 자주 걸리는 실수예요.

조건 && A에서 조건 자리에 숫자 0이 오면 문제가 생겨요. 자바스크립트에서 0은 falsy지만, 리액트는 0이라는 값을 그대로 화면에 그려버려요. 그래서 아무것도 안 나오길 기대했는데 화면에 "0"이 덩그러니 찍힙니다.

🔤
falsy — 자바스크립트에서 false처럼 "거짓"으로 취급되는 값이에요. 0, ""(빈 문자열), null, undefined, NaN이 여기에 속해요.
function Cart({ count }) {
  // ❌ count가 0이면 화면에 "0"이 그대로 찍힘!
  return (
    <div>
      {count && <p>장바구니에 {count}개 담김</p>}
    </div>
  );
}
🤔 왜 이럴까? 0 && <p>…</p>은 자바스크립트에서 왼쪽이 falsy면 그 값(0)을 그대로 반환해요. 즉 결과가 false가 아니라 숫자 0이에요. 리액트는 false·null·undefined는 화면에 안 그리지만, 0은 유효한 값이라 그대로 그려요. 🧩 이 함정은 절반은 JS(&&가 0을 반환하는 동작), 절반은 리액트(0을 화면에 그리는 규칙)의 합작이에요.

해결책은 조건을 진짜 불리언(true/false)으로 만들어 주는 거예요. count > 0처럼 비교식을 쓰면 결과가 true/false라 안전합니다.

function Cart({ count }) {
  // ✅ count > 0 은 true/false → 0일 땐 아무것도 안 나옴
  return (
    <div>
      {count > 0 && <p>장바구니에 {count}개 담김</p>}
    </div>
  );
}
⚠️
기억할 것. {배열.length && …}도 같은 함정이에요. 배열이 비면 length가 0이라 화면에 "0"이 찍혀요. 항상 length > 0 && …처럼 비교식으로 감싸세요.
📃
리스트 렌더링
배열을 map으로 목록 그리기
데이터 배열을 화면 요소 배열로 바꿔요.

화면에 여러 개를 반복해서 그릴 때는 배열의 map() 메서드를 써요. map배열의 각 항목을 JSX 요소로 변환해서 JSX 배열을 만들어요. 리액트는 그 배열을 받아 하나하나 화면에 그립니다.

🟦
.map()은 순수 자바스크립트 배열 메서드예요 — 리액트 전용이 아니라 어디서나 쓰는 JS 기능이죠. 리액트가 여기에 더하는 건 뒤에 나올 key 하나뿐이에요(그건 🟪 리액트 규칙).
function FruitList() {
  const fruits = ['사과', '바나나', '포도'];

  return (
    <ul>
      {fruits.map((fruit) => (
        <li>{fruit}</li>
      ))}
    </ul>
  );
}
// 결과: 사과 · 바나나 · 포도 세 개의 <li>

객체 배열이면 원하는 속성을 꺼내 쓰면 돼요.

const items = [
  { id: 1, name: '키보드' },
  { id: 2, name: '마우스' },
];

function ProductList() {
  return (
    <ul>
      {items.map((item) => (
        <li>{item.name}</li>
      ))}
    </ul>
  );
}
💡
핵심 한 줄. "반복 렌더링 = map." for 문 대신 map으로 데이터 배열 → JSX 배열을 만들어 꽂는다고 기억하세요.
🔑
key
각 항목엔 고유한 key가 필요해요
리액트가 항목을 추적하는 이름표예요.

map으로 만든 각 요소에는 고유한 key를 붙여야 해요. key는 리액트가 "어떤 항목이 추가·삭제·변경됐는지"를 효율적으로 추적하기 위한 이름표예요(재조정 과정에서 사용). key가 없으면 콘솔에 경고가 뜨고, 목록이 바뀔 때 비효율적으로 다시 그려질 수 있어요.

🔤
재조정(reconciliation) — 리액트가 이전 화면과 새 화면을 비교해서 실제로 바뀐 부분만 골라 다시 그리는 과정이에요. key는 이 비교를 정확하고 빠르게 만들어줘요.
const items = [
  { id: 1, name: '키보드' },
  { id: 2, name: '마우스' },
];

function ProductList() {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}
📦
데이터 배열
각 항목에 고유 id
🔑
key 부여
key={item.id}
🧮
재조정
바뀐 항목만 추적
효율적 갱신
최소한만 다시 그림
🔑 key 규칙 • key는 형제 요소 사이에서만 고유하면 돼요(전체에서 유일할 필요는 없어요).
• key는 변하지 않는 값이 좋아요 — 보통 데이터의 고유 id를 씁니다.
• key는 리액트 내부용이라 컴포넌트 안에서 props처럼 읽을 수는 없어요.
🧭
왜 필요한가. 리액트는 이전 목록과 새 목록을 key로 짝지어 비교해요. key가 있으면 "3번 항목만 바뀌었네" 하고 그것만 갱신하지만, key가 없거나 불안정하면 전부 다시 그릴 수 있어요.
🚫
index 주의
index를 key로 쓰면 안 되는 경우
편하다고 배열 index를 key로 쓰면 버그가 나요.

고유 id가 없다고 map((item, index) => <li key={index}>)처럼 배열 index를 key로 쓰는 경우가 많아요. 목록이 절대 안 바뀌면(고정 순서, 추가·삭제 없음) 괜찮지만, 목록 순서가 바뀌거나 항목을 추가·삭제하면 버그가 생겨요.

🐛 왜 문제가 되나 맨 앞에 항목을 추가하면 모든 항목의 index가 한 칸씩 밀려요. 리액트는 key(=index)로 짝을 맞추는데, index가 밀리면서 다른 항목끼리 짝지어져 입력값·포커스·상태가 엉뚱한 항목에 남는 버그가 납니다. 그래서 index는 "이름표"로서 불안정해요. 같은 항목인데 순서가 바뀌면 key가 달라지니까요.
// ❌ 순서가 바뀌거나 추가/삭제되면 버그 위험
{items.map((item, index) => (
  <li key={index}>{item.name}</li>
))}

// ✅ 데이터의 고유 id를 key로
{items.map((item) => (
  <li key={item.id}>{item.name}</li>
))}
🧩 고유 id가 정말 없다면?
데이터에 id가 없으면 데이터를 만들 때 id를 부여하는 게 가장 좋아요(예: crypto.randomUUID()). 정말 어쩔 수 없고 목록이 정적(추가·삭제·정렬 없음)이라면 그때만 index를 key로 써도 괜찮아요.
⚠️
정리. 가급적 고유 id를 key로 쓰세요. index는 "목록이 절대 안 바뀔 때"의 최후 수단이에요.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 개념결과)을 섞어 두었어요.
CH 07 ⏳ useEffect — 렌더링 후 부수효과 다루기