포스트

[이제와서 시작하는 React 마스터하기 #6] 조건부 렌더링과 리스트 - 동적 UI 만들기

[이제와서 시작하는 React 마스터하기 #6] 조건부 렌더링과 리스트 - 동적 UI 만들기

React Day 6 - 이제 버튼을 클릭하면 화면이 바뀌고, 리스트를 렌더링할 수 있어요! 오늘은 조건에 따라 다른 화면을 보여주고, 배열 데이터를 화면에 표시하는 방법을 배워봅시다.

오늘 배울 내용

  • ✅ 조건부 렌더링: if/else, 삼항 연산자, &&로 화면 제어하기
  • ✅ 리스트 렌더링: map()으로 배열 데이터 표시하기
  • ✅ key 속성: React가 리스트를 효율적으로 관리하는 방법
  • ✅ 실전 예제: 할 일 목록, 필터링, 검색 기능

시작하기 전에

이전에 배운 내용을 기억하시나요?

오늘은 이 지식을 바탕으로 조건에 따라 다른 화면을 보여주는 방법배열 데이터를 리스트로 표시하는 방법을 배워봅시다!


조건부 렌더링이란?

조건부 렌더링은 특정 조건에 따라 다른 내용을 화면에 보여주는 것을 말해요.

일상 비유 🏠

스마트폰 화면을 생각해보세요:

  • 로그인 전: 로그인 화면 표시
  • 로그인 후: 메인 화면 표시
  • 로딩 중: 로딩 스피너 표시
  • 에러 발생: 에러 메시지 표시

React도 똑같아요! 조건에 따라 다른 컴포넌트나 내용을 보여줄 수 있어요.


조건부 렌더링 방법

방법 1: if/else 문

가장 기본적인 방법이에요. 함수 안에서 조건을 확인하고, 다른 JSX를 return해요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function LoginButton() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  // 조건에 따라 다른 화면 반환
  if (isLoggedIn) {
    return (
      <div>
        <h1>환영합니다! 👋</h1>
        <button onClick={() => setIsLoggedIn(false)}>
          로그아웃
        </button>
      </div>
    );
  } else {
    return (
      <div>
        <h1>로그인이 필요합니다</h1>
        <button onClick={() => setIsLoggedIn(true)}>
          로그인
        </button>
      </div>
    );
  }
}

실행 결과:

  • 처음: “로그인이 필요합니다” + 로그인 버튼
  • 로그인 클릭 후: “환영합니다! 👋” + 로그아웃 버튼

방법 2: 삼항 연산자 (? :)

간단한 조건일 때는 삼항 연산자가 더 깔끔해요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <div>
      <h1>
        {isLoggedIn ? '환영합니다! 👋' : '로그인이 필요합니다'}
      </h1>

      <button onClick={() => setIsLoggedIn(!isLoggedIn)}>
        {isLoggedIn ? '로그아웃' : '로그인'}
      </button>
    </div>
  );
}

문법: 조건 ? 참일_때 : 거짓일_때

방법 3: && 연산자

특정 조건일 때 보여주고 싶을 때 사용해요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function App() {
  const [showMessage, setShowMessage] = useState(false);

  return (
    <div style=>
      <button onClick={() => setShowMessage(!showMessage)}>
        {showMessage ? '메시지 숨기기' : '메시지 보기'}
      </button>

      {/* showMessage가 true일 때만 메시지 표시 */}
      {showMessage && (
        <div style=>
          안녕하세요! 이것은 조건부 메시지입니다 ✨
        </div>
      )}
    </div>
  );
}

문법: 조건 && 표시할_내용


실습 1: 로딩/에러/성공 상태 관리

실제 앱에서 자주 사용하는 패턴이에요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function App() {
  const [status, setStatus] = useState('idle'); // 'idle', 'loading', 'success', 'error'
  const [data, setData] = useState(null);

  const fetchData = () => {
    setStatus('loading');

    // 2초 후 랜덤으로 성공/실패
    setTimeout(() => {
      if (Math.random() > 0.5) {
        setData({ name: '홍길동', age: 25 });
        setStatus('success');
      } else {
        setStatus('error');
      }
    }, 2000);
  };

  return (
    <div style=>
      <h1>데이터 가져오기</h1>

      <button onClick={fetchData}>
        데이터 로드
      </button>

      {/* 상태에 따른 조건부 렌더링 */}
      {status === 'idle' && (
        <p>버튼을 클릭해서 데이터를 불러오세요</p>
      )}

      {status === 'loading' && (
        <p>⏳ 로딩 중...</p>
      )}

      {status === 'success' && data && (
        <div style=>
          <h2>✅ 성공!</h2>
          <p>이름: {data.name}</p>
          <p>나이: {data.age}</p>
        </div>
      )}

      {status === 'error' && (
        <div style=>
          <h2>❌ 에러 발생!</h2>
          <p>데이터를 불러오는데 실패했습니다.</p>
          <button onClick={fetchData}>다시 시도</button>
        </div>
      )}
    </div>
  );
}

해보기:

  1. 위 코드를 복사해서 실행해보세요
  2. “데이터 로드” 버튼을 여러 번 클릭해보세요
  3. 로딩 → 성공/에러 화면이 어떻게 바뀌는지 관찰하세요

리스트 렌더링이란?

리스트 렌더링은 배열 데이터를 화면에 반복해서 표시하는 것을 말해요.

일상 비유 📝

쇼핑 목록을 생각해보세요:

1
2
3
4
5
쇼핑 목록:
1. 사과
2. 바나나
3. 우유
4. 빵

React에서는 이런 리스트를 map() 함수로 만들어요!


map()으로 리스트 만들기

기본 사용법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function App() {
  const fruits = ['사과', '바나나', '오렌지', '포도'];

  return (
    <div style=>
      <h1>과일 목록</h1>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

결과:

1
2
3
4
5
과일 목록
• 사과
• 바나나
• 오렌지
• 포도

객체 배열 렌더링

실제 앱에서는 보통 객체 배열을 다뤄요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function App() {
  const users = [
    { id: 1, name: '홍길동', age: 25 },
    { id: 2, name: '김영희', age: 30 },
    { id: 3, name: '이철수', age: 28 }
  ];

  return (
    <div style=>
      <h1>사용자 목록</h1>
      {users.map(user => (
        <div
          key={user.id}
          style=
        >
          <h3>{user.name}</h3>
          <p>나이: {user.age}</p>
        </div>
      ))}
    </div>
  );
}

key 속성이 왜 필요할까?

리스트를 렌더링할 때 key 속성을 꼭 넣어야 해요. React가 리스트의 각 항목을 추적하기 위해 필요하답니다.

key 사용 규칙

좋은 예: 고유한 id 사용

1
2
3
{users.map(user => (
  <div key={user.id}>{user.name}</div>
))}

⚠️ 피해야 할 예: index 사용 (항목이 추가/삭제/정렬되는 경우)

1
2
3
{users.map((user, index) => (
  <div key={index}>{user.name}</div>
))}

왜 index는 피해야 할까요?

항목이 추가되거나 삭제되면 index가 바뀌어서 React가 혼란스러워해요. 예를 들어:

  • 원래: [0: 사과, 1: 바나나, 2: 오렌지]
  • ‘사과’ 삭제 후: [0: 바나나, 1: 오렌지]
  • React: “어? 0번이 ‘사과’에서 ‘바나나’로 바뀌었네?” 😵

하지만 고유한 id를 쓰면:

  • 원래: [id:1 사과, id:2 바나나, id:3 오렌지]
  • 삭제 후: [id:2 바나나, id:3 오렌지]
  • React: “id:1이 사라졌구나! 나머지는 그대로네” 😊

실습 2: 할 일 목록 (Todo List)

리스트 렌더링의 대표적인 예제예요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'React 공부하기', completed: false },
    { id: 2, text: '장보기', completed: true },
    { id: 3, text: '운동하기', completed: false }
  ]);
  const [input, setInput] = useState('');

  // 할 일 추가
  const addTodo = () => {
    if (input.trim() === '') return;

    const newTodo = {
      id: Date.now(), // 간단한 고유 ID
      text: input,
      completed: false
    };

    setTodos([...todos, newTodo]);
    setInput('');
  };

  // 완료 상태 토글
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };

  // 할 일 삭제
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div style=>
      <h1>📝 할 일 목록</h1>

      {/* 입력 폼 */}
      <div style=>
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="할 일을 입력하세요"
          style=
        />
        <button
          onClick={addTodo}
          style=
        >
          추가
        </button>
      </div>

      {/* 할 일 리스트 */}
      {todos.length === 0 ? (
        <p style=>
          할 일이 없습니다. 추가해보세요!
        </p>
      ) : (
        <div>
          {todos.map(todo => (
            <div
              key={todo.id}
              style=
            >
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
                style=
              />

              <span
                style=
              >
                {todo.text}
              </span>

              <button
                onClick={() => deleteTodo(todo.id)}
                style=
              >
                삭제
              </button>
            </div>
          ))}
        </div>
      )}

      {/* 통계 */}
      <div style=>
        전체: {todos.length}개 |
        완료: {todos.filter(t => t.completed).length}개 |
        미완료: {todos.filter(t => !t.completed).length}</div>
    </div>
  );
}

해보기:

  1. 할 일을 추가해보세요
  2. 체크박스를 클릭해서 완료 상태를 바꿔보세요 (취소선 생김)
  3. 삭제 버튼으로 항목을 지워보세요
  4. 통계가 실시간으로 업데이트되는지 확인하세요

실습 3: 필터링 기능 추가

할 일 목록에 필터링을 추가해봅시다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'React 공부하기', completed: false },
    { id: 2, text: '장보기', completed: true },
    { id: 3, text: '운동하기', completed: false },
    { id: 4, text: '청소하기', completed: true }
  ]);
  const [filter, setFilter] = useState('all'); // 'all', 'active', 'completed'

  // 필터링된 할 일 목록
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true; // 'all'
  });

  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  return (
    <div style=>
      <h1>📝 할 일 목록 (필터링)</h1>

      {/* 필터 버튼 */}
      <div style=>
        <button
          onClick={() => setFilter('all')}
          style=
        >
          전체 ({todos.length})
        </button>

        <button
          onClick={() => setFilter('active')}
          style=
        >
          진행중 ({todos.filter(t => !t.completed).length})
        </button>

        <button
          onClick={() => setFilter('completed')}
          style=
        >
          완료 ({todos.filter(t => t.completed).length})
        </button>
      </div>

      {/* 필터링된 리스트 */}
      {filteredTodos.length === 0 ? (
        <p style=>
          {filter === 'active' && '진행 중인 할 일이 없습니다!'}
          {filter === 'completed' && '완료된 할 일이 없습니다!'}
          {filter === 'all' && '할 일이 없습니다!'}
        </p>
      ) : (
        filteredTodos.map(todo => (
          <div
            key={todo.id}
            style=
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
              style=
            />

            <span
              style=
            >
              {todo.text}
            </span>
          </div>
        ))
      )}
    </div>
  );
}

해보기:

  1. 필터 버튼을 클릭해보세요 (전체/진행중/완료)
  2. 체크박스를 클릭해서 완료 상태를 바꿔보세요
  3. 필터가 실시간으로 업데이트되는지 확인하세요

실습 4: 검색 기능

사용자 목록에 검색 기능을 추가해봅시다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
function App() {
  const [users] = useState([
    { id: 1, name: '홍길동', email: 'hong@example.com', age: 25 },
    { id: 2, name: '김영희', email: 'kim@example.com', age: 30 },
    { id: 3, name: '이철수', email: 'lee@example.com', age: 28 },
    { id: 4, name: '박민수', email: 'park@example.com', age: 35 },
    { id: 5, name: '정수진', email: 'jung@example.com', age: 27 }
  ]);
  const [searchTerm, setSearchTerm] = useState('');

  // 검색 필터링
  const filteredUsers = users.filter(user =>
    user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
    user.email.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div style=>
      <h1>👥 사용자 검색</h1>

      {/* 검색 입력 */}
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="이름 또는 이메일로 검색..."
        style=
      />

      {/* 검색 결과 통계 */}
      <p style=>
        {searchTerm
          ? `"${searchTerm}" 검색 결과: ${filteredUsers.length}명`
          : `전체 사용자: ${users.length}명`
        }
      </p>

      {/* 사용자 리스트 */}
      {filteredUsers.length === 0 ? (
        <div style=>
          <h2>😢</h2>
          <p>검색 결과가 없습니다</p>
        </div>
      ) : (
        filteredUsers.map(user => (
          <div
            key={user.id}
            style=
          >
            <h3 style=>
              {user.name}
            </h3>
            <p style=>
              📧 {user.email}
            </p>
            <p style=>
              🎂 {user.age}</p>
          </div>
        ))
      )}
    </div>
  );
}

해보기:

  1. 검색창에 “홍” 또는 “kim”을 입력해보세요
  2. 검색어를 지우면 전체 목록이 다시 나타나는지 확인하세요
  3. 검색 결과 통계가 업데이트되는지 확인하세요

흔한 실수와 해결 방법

실수 1: key를 빼먹음

잘못된 코드:

1
2
3
{users.map(user => (
  <div>{user.name}</div>  // key가 없어요!
))}

⚠️ 콘솔 경고: “Warning: Each child in a list should have a unique ‘key’ prop.”

올바른 코드:

1
2
3
{users.map(user => (
  <div key={user.id}>{user.name}</div>
))}

실수 2: key로 index 사용 (항목이 변경되는 경우)

피해야 할 코드:

1
2
3
{todos.map((todo, index) => (
  <div key={index}>{todo.text}</div>  // 항목이 추가/삭제되면 문제!
))}

올바른 코드:

1
2
3
{todos.map(todo => (
  <div key={todo.id}>{todo.text}</div>  // 고유한 id 사용
))}

실수 3: 조건부 렌더링에서 false/null/undefined 실수

주의해야 할 코드:

1
{count && <p>카운트: {count}</p>}  // count가 0이면 화면에 "0"이 표시됨!

올바른 코드:

1
{count > 0 && <p>카운트: {count}</p>}  // 명확한 조건

또는

1
{count !== 0 && <p>카운트: {count}</p>}

실수 4: map()에서 직접 return 안 함

잘못된 코드:

1
2
3
{users.map(user => {
  <div>{user.name}</div>  // return이 없어요!
})}

올바른 코드 (중괄호 사용 시 return 필요):

1
2
3
{users.map(user => {
  return <div key={user.id}>{user.name}</div>;
})}

또는 (괄호 사용 - return 생략 가능):

1
2
3
{users.map(user => (
  <div key={user.id}>{user.name}</div>
))}

정리

오늘은 조건부 렌더링과 리스트 렌더링을 배웠어요!

핵심 포인트 ✅

조건부 렌더링:

  • if/else: 완전히 다른 JSX를 반환할 때
  • 삼항 연산자 (? :): 간단한 조건일 때
  • && 연산자: 특정 조건일 때만 보여줄 때

리스트 렌더링:

  • map() 함수로 배열을 JSX로 변환
  • key 속성 필수: 고유한 id 사용 (index는 피하기)
  • filter()로 조건에 맞는 항목만 표시

실전 패턴:

  • 로딩/에러/성공 상태 관리
  • 할 일 목록 (추가/삭제/완료)
  • 필터링과 검색 기능

오늘 배운 내용 체크 ✅

  • if/else, 삼항 연산자, && 연산자의 차이를 이해했나요?
  • map()으로 배열을 리스트로 변환할 수 있나요?
  • key 속성이 왜 필요한지 이해했나요?
  • filter()로 리스트를 필터링할 수 있나요?
  • 할 일 목록 앱을 직접 만들어봤나요?

숙제 📚

  1. 쇼핑 카트 만들기: 상품 추가/삭제, 총 가격 계산
  2. 게시판 만들기: 글 목록, 검색, 페이지네이션
  3. 날씨 앱: 도시별 날씨 정보 표시 (조건부 아이콘)

다음 단계

다음 포스트에서는 Context API를 배워봅니다! Props를 여러 컴포넌트에 전달하는 불편함을 해결하는 방법을 알아봐요.

“늦었다고 생각할 때가 가장 빠를 때입니다. 오늘도 한 걸음 나아갔어요! 🎉”


React 완벽 가이드 시리즈

  1. React란 무엇인가? 시작하기 전 알아야 할 모든 것
  2. 첫 React 앱 만들기 - JSX와 컴포넌트
  3. useState - 클릭하면 바뀌는 화면 만들기
  4. Props - 컴포넌트끼리 대화하기
  5. useEffect - 컴포넌트 생명주기
  6. 조건부 렌더링과 리스트 - 동적 UI 만들기 ← 현재 글
  7. Context API로 전역 상태 관리하기
  8. Custom Hooks 만들기
  9. React Router 완벽 가이드
  10. 폼 처리와 유효성 검증
  11. React 성능 최적화
  12. 에러 바운더리와 Suspense
  13. 상태 관리 라이브러리
  14. 서버 상태 관리
  15. React와 TypeScript
  16. Next.js 입문
  17. React 테스팅 전략
  18. 고급 패턴과 베스트 프랙티스
  19. 실전 프로젝트
  20. React 배포와 DevOps

관련 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.