State와 화면 갱신
6강. State와 화면 갱신
1. 이번 강의에서 해결할 문제
관심 게임 버튼을 눌러도 현재 카드는 변하지 않습니다. 사용자 행동의 결과를 기억하고 즉시 화면에 반영해야 합니다.
2. 학습 목표
- 일반 변수와 state의 차이를 설명한다.
- useState로 컴포넌트 상태를 선언하고 갱신한다.
- 이전 상태를 바탕으로 안전하게 다음 상태를 계산한다.
3. 핵심 개념
state는 렌더링 사이에 React가 보존하는 컴포넌트의 기억입니다. useState는 현재 값과 값을 바꾸는 setter를 반환합니다. setter를 호출하면 React가 새 상태로 컴포넌트를 다시 렌더링합니다.
일반 지역 변수는 컴포넌트 함수가 실행될 때마다 다시 만들어지고, 값을 바꿔도 React에 화면 재계산을 요청하지 않습니다. 반면 state는 React가 컴포넌트와 연결해 보존하며 setter 호출을 다음 렌더링의 입력으로 사용합니다.
props·일반 변수·state 비교
| 종류 | 누가 정하는가 | 변경 시 화면 갱신 | 사용 예 |
|---|---|---|---|
| props | 부모 컴포넌트 | 부모가 새 값을 전달하면 갱신 | 게임 제목, 출시일 |
| 일반 변수 | 현재 함수 실행 | 직접 변경해도 렌더링을 요청하지 않음 | 렌더링 중 계산한 임시 값 |
| state | 컴포넌트가 setter로 요청 | React가 다시 렌더링 | 관심 게임 여부, 조회수 |
가장 작은 state 예제
const [count, setCount] = useState(0);
function increase() {
setCount((current) => current + 1);
}
구조 분해 할당의 첫 값 count는 현재 렌더링의 값이고, 둘째 setCount는 다음 값을 요청하는 함수입니다. current는 React가 보관한 가장 최신 값을 받으므로 연속 갱신에도 안전합니다.
state를 직접 바꾸지 않고 setter를 사용해야 React가 변경을 알고 화면과 상태를 일관되게 유지할 수 있습니다.
4. 단계별 코드 예시
파일 경로: game-release-dashboard/src/components/GameCard.tsx
실행 전에는 버튼 문구가 고정되어 관심 여부를 기억하지 못합니다. 아래 코드에서는 첫 화면에 관심 게임 추가가 보이고, 클릭할 때마다 추가와 해제가 번갈아 나타납니다.
import { useState } from 'react';
type GameCardProps = {
title: string;
releaseDate: string;
platform: string;
};
export function GameCard(props: GameCardProps) {
const [isFavorite, setIsFavorite] = useState(false);
function toggleFavorite() {
setIsFavorite((current) => !current);
}
return (
<article className="game-card">
<p>{props.releaseDate}</p>
<h2>{props.title}</h2>
<p>{props.platform}</p>
<button type="button" onClick={toggleFavorite}>
{isFavorite ? '관심 게임 해제' : '관심 게임 추가'}
</button>
</article>
);
}
중요한 줄을 따라가기
import { useState }로 React Hook을 가져옵니다.useState(false)가 첫 렌더링의 관심 여부를 거짓으로 정합니다.setIsFavorite((current) => !current)는 가장 최신 값을 받아 참과 거짓을 뒤집습니다.onClick={toggleFavorite}는 함수를 지금 실행하지 않고 클릭할 때 실행하도록 전달합니다.onClick={toggleFavorite()}로 쓰면 렌더링 중 호출됩니다.- 삼항 연산자는
isFavorite이 참이면관심 게임 해제, 거짓이면관심 게임 추가를 반환합니다.
클릭 전후 동작 흐름
| 순서 | isFavorite | 실행 | 화면 문구 |
|---|---|---|---|
| 첫 렌더링 | false | 초기값 사용 | 관심 게임 추가 |
| 첫 클릭 | 현재 false를 반전 요청 | setIsFavorite(true) 효과 | 다음 렌더링에서 관심 게임 해제 |
| 둘째 클릭 | 현재 true를 반전 요청 | setIsFavorite(false) 효과 | 다음 렌더링에서 관심 게임 추가 |
setter를 호출한 같은 이벤트 함수 안에서 기존 isFavorite을 출력하면 아직 현재 렌더링의 스냅샷이 보일 수 있습니다. 새 값은 React가 다음 렌더링을 계산할 때 사용됩니다.
5. 코드가 동작하는 이유
첫 렌더링에서 isFavorite은 false입니다. 클릭하면 setter가 이전 값을 반대로 바꾸고 다시 렌더링합니다. 새 렌더링의 삼항 연산자는 true에 맞는 문구를 반환합니다. 함수형 갱신은 연속된 업데이트에서도 가장 최신 상태를 입력으로 받습니다.
6. 자주 하는 실수와 해결법
- 일반 변수만 변경: 렌더링 때 초기화되고 React도 변경을 모릅니다. 화면에 영향을 주는 기억은 state로 둡니다.
isFavorite = true처럼 직접 변경: setter를 호출합니다.- setter 직후 값이 즉시 바뀌었다고 가정: 현재 이벤트의 state는 스냅샷입니다. 다음 렌더링에서 새 값을 사용합니다.
- Hook을 조건문 안에서 호출: Hook은 컴포넌트 최상위에서 같은 순서로 호출합니다.
7. 직접 실습
실습 목표
카드에 미리보기 횟수 state를 추가하고 클릭마다 화면이 1씩 증가하는 흐름을 확인합니다.
시작 전 상태
관심 게임 토글이 정상 작동하는 GameCard.tsx에서 시작합니다. 개발자 도구 Console 오류가 없는지 확인합니다.
1단계: 따라 하기
const [previewCount, setPreviewCount] = useState(0);을 컴포넌트 최상위에 추가합니다. 새 버튼과 <p>미리보기 {previewCount}회</p>를 렌더링하고 클릭에서 함수형 갱신으로 1을 더합니다.
2단계: 값을 바꾸고 결과 비교
초기값을 0에서 5로 바꿔 첫 화면과 첫 클릭 결과를 비교합니다. 이어서 증가량을 1에서 2로 바꾸고 5 → 7 → 9 순서가 되는지 확인합니다.
3단계: 직접 적용
미리보기 초기화 버튼을 추가해 값을 0으로 만듭니다. 0일 때는 아직 미리보지 않음, 1 이상이면 횟수를 표시하도록 조건부 문구를 완성합니다.
4단계: 스스로 확인
- state Hook을 조건문 밖 컴포넌트 최상위에서 호출했나요?
- 이전 값에 의존하는 증가는 함수형 setter를 사용했나요?
- 버튼을 눌렀을 때 값과 화면이 함께 바뀌나요?
막혔을 때
- 클릭해도 숫자가 그대로: 일반 변수를 변경하지 않았는지, setter가 실제 클릭 함수에서 호출되는지 확인합니다.
- 무한 렌더링: JSX의
onClick에 함수 호출 결과가 아니라 함수 자체를 전달합니다. - Hook 순서 오류:
useState를 조건문·반복문·중첩 함수 밖으로 옮깁니다. - 한 번에 여러 증가가 일부만 반영:
setPreviewCount((current) => current + 1)처럼 최신 값을 입력받습니다.
8. 이해 점검 질문 3개
- 일반 변수를 바꿔도 React 화면이 갱신되지 않는 이유는 무엇인가요?
- 한 클릭에서 조회수를 두 번 연속 증가시켜야 할 때 함수형 setter가 안전한 이유는 무엇인가요?
- 컴포넌트가 다시 렌더링되어도 state가 유지되는 이유는 무엇인가요?
9. 핵심 요약
state는 UI에 영향을 주는 컴포넌트의 기억입니다. setter로 새 상태를 요청하면 React가 화면을 다시 계산합니다.
다음 강의 연결
state를 바꾸는 출발점은 사용자 입력입니다. 7강. 이벤트와 입력에서 클릭뿐 아니라 입력 필드의 변경 값을 읽어 검색 상태와 연결합니다.
React 상태 관리 점검
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.