React 기초 (컴포넌트, JSX, 상태 관리)
Vite로 React를 시작하고 JSX·컴포넌트·state·props로 데이터를 받아 상호작용하는 선언형 UI를 만듭니다.
앞 글 SPA 개념에서는 SPA가 무엇이고 왜 널리 쓰이게 되었는지 살펴봤습니다. SPA는 앱처럼 매끄러운 경험을 주지만, 그러려면 브라우저에서 화면을 계속 바꾸는 복잡한 코드가 필요합니다. 이런 화면 개발을 쉽게 해 주는 것이 프런트엔드 프레임워크와 라이브러리이며, 그 가운데 Meta(예전 Facebook)가 만든 React는 화면을 선언적으로 만들고 관리하게 해 주는 UI 라이브러리입니다.
이 글에서는 React의 핵심 개념인 컴포넌트(component), JSX, 상태(state), 속성(props)을 중심으로 React 애플리케이션을 만드는 기본 방법을 익힙니다. 글을 쓰는 시점의 React 최신 버전은 19.3이고, 개발 환경은 Vite 8로 만듭니다.
React의 등장 배경과 특징
React는 2013년에 오픈 소스로 공개되었습니다. 당시에는 jQuery 같은 라이브러리로 DOM을 직접 조작하는 방식이 주류였는데, 애플리케이션이 커지고 화면이 복잡해지면서 DOM 조작을 관리하기 어려워지고 성능 문제도 생겼습니다. React는 이 문제에 대한 해결책으로 나왔으며, 몇 가지 특징을 가집니다.
먼저 선언형(declarative)입니다. DOM을 하나하나 직접 바꾸는 명령형(imperative) 방식과 달리, 화면이 어떻게 보여야 하는지를 선언하면 React가 그 모습이 되도록 DOM을 갱신합니다. 또 컴포넌트를 기반으로 합니다. 화면을 따로 떼어 다시 쓸 수 있는 작은 조각인 컴포넌트로 나눠 만들므로, 코드를 다시 쓰기 쉽고 유지보수가 편합니다.
React는 화면을 메모리 안의 구조로 표현하고, 렌더(render)에서 다음 화면을 계산한 뒤 커밋(commit) 단계에서 필요한 DOM 변경만 반영합니다. 이 메모리 안의 구조를 흔히 가상 DOM(virtual DOM)이라고 부르는데, 성능은 계산 비용과 다시 그리는 범위에 따라 달라지므로 DOM을 직접 다루는 것보다 항상 빠르다고 보장하지는 않습니다. 마지막으로 데이터는 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 한 방향으로 흐릅니다. 이 단방향 데이터 흐름(unidirectional data flow) 덕분에 데이터가 어디서 왔는지 예측하고 디버깅하기 쉽습니다.
React 렌더·커밋 생명주기 — 상태 추적
사용자 이벤트→state queue→순수 render→DOM commit→paint의 서로 다른 관찰 시점을 분리하면 setState 직후 DOM이 바뀐다는 오해를 줄인다.
PROCESS · B99 WEB FUNDAMENTALS
React 렌더·커밋 생명주기
이벤트 핸들러가 state update를 예약하면 React가 컴포넌트를 render·reconcile하고, commit 단계에서 필요한 host DOM 변경만 적용한 뒤 브라우저가 paint한다.
렌더는 다음 UI를 계산하고, 커밋은 필요한 DOM 변경을 반영한다. Virtual DOM이 모든 앱의 성능을 자동으로 보장하지는 않는다. useEffect는 상호작용과 일정에 따라 paint 전이나 후에 실행될 수 있다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 이벤트의 snapshot | state: 현재 렌더 값 / DOM: 이전 commit | handler는 현재 props/state를 읽는다 | 사용자 사건이 다음 상태 요청을 만든다 |
| 다음 UI 계산 | state: 다음 렌더 값 / DOM: 아직 이전 commit | 업데이트를 적용해 render 및 결과 비교를 수행한다 | 계산과 실제 DOM 변경은 별개다 |
| 필요한 DOM commit | DOM: 변경 부분 반영 / 화면: 새 표시 | 달라진 부분을 반영한 뒤 브라우저가 표시한다 | render 결과를 commit해야 실제 DOM이 바뀐다 |
React 렌더·커밋 생명주기 — 상태 추적
사용자 이벤트→state queue→순수 render→DOM commit→paint의 서로 다른 관찰 시점을 분리하면 setState 직후 DOM이 바뀐다는 오해를 줄인다.
PROCESS · B99 WEB FUNDAMENTALS
React 렌더·커밋 생명주기
이벤트 핸들러가 state update를 예약하면 React가 컴포넌트를 render·reconcile하고, commit 단계에서 필요한 host DOM 변경만 적용한 뒤 브라우저가 paint한다.
렌더는 다음 UI를 계산하고, 커밋은 필요한 DOM 변경을 반영한다. Virtual DOM이 모든 앱의 성능을 자동으로 보장하지는 않는다. useEffect는 상호작용과 일정에 따라 paint 전이나 후에 실행될 수 있다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 이벤트의 snapshot | state: 현재 렌더 값 / DOM: 이전 commit | handler는 현재 props/state를 읽는다 | 사용자 사건이 다음 상태 요청을 만든다 |
| 다음 UI 계산 | state: 다음 렌더 값 / DOM: 아직 이전 commit | 업데이트를 적용해 render 및 결과 비교를 수행한다 | 계산과 실제 DOM 변경은 별개다 |
| 필요한 DOM commit | DOM: 변경 부분 반영 / 화면: 새 표시 | 달라진 부분을 반영한 뒤 브라우저가 표시한다 | render 결과를 commit해야 실제 DOM이 바뀐다 |
위 그림은 사용자의 입력 하나가 화면에 반영되기까지의 다섯 단계입니다. 먼저 이벤트 핸들러가 사용자가 하려는 일을 읽는데, 여기서 DOM을 직접 고치지는 않습니다. 핸들러가 상태를 바꾸면 새 값이나 갱신 함수가 대기열에 예약되고, 여러 갱신은 한꺼번에 묶어 처리될 수 있습니다.
그다음 React가 컴포넌트를 다시 호출해 다음 화면을 계산하고 이전 결과와 비교하는 렌더 단계를 거칩니다. 렌더는 순수한 계산이라 필요하면 다시 시작될 수도 있습니다. 이어지는 커밋 단계에서는 필요한 DOM 변경과 ref 연결, 레이아웃 효과를 반영하며, 이 단계는 중간에 멈추지 않습니다. 마지막으로 브라우저가 화면을 그리고(paint) 다음 입력을 기다리며, 점선은 다음 상호작용으로 다시 처음부터 시작하는 흐름입니다. 그림 아래의 설명처럼 가상 DOM이 모든 앱의 성능을 저절로 보장하지는 않으며, useEffect는 상호작용의 종류와 일정에 따라 화면을 그리기 전이나 후에 실행될 수 있습니다.
Vite로 개발 환경 만들기
React 개발 환경을 가장 간단하게 만드는 방법은 Vite를 쓰는 것입니다. Vite는 빠른 개발 서버와 간단한 기본 설정을 제공합니다. Vite 개발 도구를 실행하려면 Node.js가 필요하며, 만들어진 React 화면은 브라우저에서 실행됩니다. Vite 공식 안내에 따르면 Vite 8은 Node.js 20.19 이상이나 22.12 이상이 필요하고, 고른 템플릿이 더 높은 버전을 요구할 수도 있으므로 생성 명령의 안내도 확인하세요.
먼저 Node.js와 npm이 설치되어 있는지 확인합니다.
node -v
npm -v
# 또는
yarn -v새 React 프로젝트를 만듭니다. my-react-app은 만들 프로젝트의 이름이고, 실행 중에 몇 가지 선택을 묻는 질문이 나오면 안내에 따라 고르면 됩니다.
npm create vite@latest my-react-app -- --template react명령 가운데 -- 뒤의 --template react는 npm이 아니라 프로젝트를 만드는 create-vite에 넘기는 옵션으로, React 템플릿으로 만들라는 뜻입니다.
프로젝트 폴더로 이동해 의존성을 설치합니다. npm install은 package.json에 적힌 React와 Vite 같은 패키지를 node_modules 폴더에 내려받습니다.
cd my-react-app
npm install개발 서버를 실행합니다. npm run dev는 package.json의 scripts에 적힌 dev 명령, 즉 Vite 개발 서버를 실행합니다.
npm run dev
# 또는
yarn dev개발 서버가 시작되면 기본으로 http://localhost:5173에서 React 애플리케이션을 볼 수 있습니다. 코드를 고치고 저장하면 화면이 바로 갱신됩니다.
JSX
React에서 화면을 정의할 때 쓰는 문법 확장이 JSX입니다. JSX를 쓰면 자바스크립트 코드 안에 HTML과 비슷한 마크업을 적을 수 있어, 화면의 구조와 그 화면을 다루는 로직을 한 파일에서 읽기 쉽게 관리할 수 있습니다.
JSX 안에서는 중괄호 {}로 자바스크립트 표현식을 넣을 수 있습니다. HTML의 class 대신 className을 쓰는 것처럼 대부분의 속성은 onClick 같은 카멜 표기(camelCase)를 쓰지만, aria-*와 data-*는 하이픈을 그대로 둡니다. 내용이 없는 태그는 <img />, <input />처럼 반드시 />로 닫아야 하고, 컴포넌트가 여러 요소를 돌려줄 때는 <div>나 <>...</> 같은 하나의 최상위 요소로 감싸야 합니다.
import './App.css'; // CSS 파일 임포트
function App() {
const name = "리액트";
const isLoggedIn = true;
return (
// JSX는 하나의 최상위 요소로 감싸야 함
<div className="App">
<header className="App-header">
<h1>Hello, {name}!</h1> {/* JSX 안에서 자바스크립트 표현식 사용 */}
{isLoggedIn ? ( // 조건부 렌더링
<p>환영합니다!</p>
) : (
<p>로그인 해주세요.</p>
)}
<button onClick={() => alert('버튼 클릭됨!')}>클릭하세요</button> {/* 이벤트 핸들러 */}
</header>
</div>
);
}
export default App; // 컴포넌트 내보내기{name}은 변수의 값으로 바뀌고, 삼항 연산자로 조건에 따라 다른 요소를 보여 줍니다. JSX 안의 주석은 {/* */}처럼 중괄호로 감싸 적습니다. JSX는 브라우저가 바로 이해하는 문법이 아니라서 일반 자바스크립트로 변환된 뒤 실행되는데, Vite가 이 변환을 자동으로 해 줍니다. React 17부터는 JSX를 쓰는 파일에서 import React from 'react'를 적지 않아도 됩니다.
컴포넌트
React 애플리케이션은 따로 떼어 다시 쓸 수 있는 작은 블록인 컴포넌트를 조합해 만듭니다. 레고 블록을 조립하듯 작은 컴포넌트를 모아 복잡한 화면을 만듭니다.
컴포넌트는 함수로 작성하는 함수 컴포넌트(function component)와, React.Component를 상속한 클래스로 작성하는 클래스 컴포넌트(class component)가 있습니다. React 16.8에서 함수 컴포넌트가 상태 같은 React 기능을 쓸 수 있게 해 주는 Hook이 추가된 뒤로는 함수 컴포넌트가 주로 쓰이며, 이 글도 함수 컴포넌트를 기준으로 설명합니다. 뒤에서 볼 useState처럼 이름이 use로 시작하는 함수가 Hook입니다.
// Props를 인자로 받음
function Greeting(props) {
return (
<h2>안녕하세요, {props.name}님!</h2>
);
}
export default Greeting;import Greeting from './Greeting.jsx'; // Greeting 컴포넌트 임포트
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>React 기초</h1>
<Greeting name="개발자" /> {/* Greeting 컴포넌트 사용, name 속성 전달 */}
<Greeting name="리액트 사용자" /> {/* 여러 번 다시 쓸 수 있음 */}
</header>
</div>
);
}
export default App;Greeting 컴포넌트는 name이라는 props를 받아 인사말을 표시합니다. 같은 컴포넌트에 다른 값을 넘기면 다른 내용으로 여러 번 쓸 수 있습니다. 직접 만든 컴포넌트의 이름은 Greeting처럼 대문자로 시작해야 하며, 소문자로 시작하면 React가 HTML 태그로 해석합니다.
상태
상태(state)는 컴포넌트 안에서 시간에 따라 바뀌는 데이터입니다. 버튼 클릭이나 글자 입력 같은 사용자의 동작에 따라 화면이 바뀌어야 할 때 상태를 씁니다. 함수 컴포넌트에서는 useState Hook으로 상태를 만듭니다. Hook은 컴포넌트나 직접 만든 Hook의 맨 위에서만 호출해야 하며, 조건문이나 반복문 안에서 호출하면 렌더마다 호출 순서가 달라져 상태가 엉킬 수 있습니다.
import { useState } from 'react'; // useState Hook 임포트
function Counter() {
// [현재 상태 값, 상태를 바꾸는 함수] = useState(초기값)
const [count, setCount] = useState(0); // count는 0으로 시작
const increment = () => {
setCount(count + 1); // setCount로 상태 갱신
};
const decrement = () => {
setCount(count - 1);
};
return (
<div>
<h3>카운터: {count}</h3>
<button onClick={increment}>증가</button>
<button onClick={decrement}>감소</button>
</div>
);
}
export default Counter;useState(초기값)은 [현재 상태 값, 상태를 바꾸는 함수] 배열을 돌려주며, 배열 구조 분해로 두 이름을 받습니다. 상태는 count++처럼 직접 바꾸지 말고, setCount() 같은 설정 함수에 새 값을 넘겨 바꿔야 합니다. 새 값이 이전 값과 Object.is로 같으면 React는 그 갱신의 렌더를 건너뛸 수 있습니다. 부모가 다시 렌더되거나 컨텍스트(context)가 바뀌어도 컴포넌트는 다시 렌더될 수 있습니다.
클릭 한 번을 끝까지 따라가기
처음 화면이 카운터: 0일 때 증가 버튼을 누르면 이렇게 진행됩니다. 지금 렌더가 만든 핸들러 안에서 count는 0이므로, setCount(count + 1)은 다음 상태를 1로 예약합니다. 그러면 React는 컴포넌트를 다시 호출해 count가 1인 JSX를 계산하는 렌더를 거치고, 계산한 결과에 필요한 DOM 변경을 반영하는 커밋을 거쳐 브라우저가 새 화면을 그립니다. 컴포넌트를 다시 호출했다고 DOM 전체를 바꾸는 것은 아니며, 바뀐 글자만 갱신됩니다.
상태 설정 함수는 이미 실행 중인 핸들러의 count를 바꾸지 않습니다. 그래서 다음 코드의 로그는 0이고, 다음 렌더의 화면에는 1이 보입니다. 렌더와 커밋의 차이는 React의 렌더와 커밋 문서에서 자세히 볼 수 있습니다.
// count가 0인 렌더에서 만든 이벤트 핸들러 안
setCount(count + 1);
console.log(count); // 0: 이 핸들러가 읽는 상태 스냅샷이전 상태를 바탕으로 여러 번 갱신할 때는 갱신 함수(updater function)를 넘깁니다. setCount(previous => previous + 1)은 대기열에서 앞의 갱신 결과를 받아 다음 값을 계산합니다. 갱신 함수 안에서는 로그를 서버로 보내거나 다른 상태를 바꾸는 부수 효과를 실행하지 않아야 합니다(React의 상태 갱신 대기열 문서).
더 깊이: 개발 중에는 렌더가 두 번 실행되는 이유
Vite로 만든 React 프로젝트의 src/main.jsx를 열어 보면 <App />이 <StrictMode>로 감싸져 있습니다. 엄격 모드(Strict Mode)는 개발 중에만 켜지는 검사 기능으로, 렌더가 순수하지 않아 생기는 버그를 찾으려고 컴포넌트를 한 번 더 렌더합니다. 그래서 컴포넌트 본문에 console.log()를 넣으면 한 번의 상태 변화에 로그가 두 번 찍히는 것처럼 보이며, React 개발자 도구를 설치했다면 두 번째 렌더의 로그는 흐리게 표시됩니다.
이 검사는 앞에서 말한 규칙, 즉 렌더와 갱신 함수가 순수해야 한다는 규칙을 지키는지 확인하는 장치입니다. 같은 입력으로 두 번 호출해도 결과가 같아야 하는데, 렌더 중에 바깥의 배열에 값을 넣거나 갱신 함수 안에서 요청을 보내면 두 번 실행되면서 결과가 달라져 문제가 드러납니다. 엄격 모드는 같은 이유로 이펙트(Effect)와 ref 콜백도 한 번 더 실행해, 정리 코드가 빠진 곳을 찾게 합니다.
이런 추가 실행은 개발 환경에서만 일어나고 배포용 빌드에는 영향을 주지 않습니다. 로그가 두 번 찍힌다고 <StrictMode>를 지우기보다, 두 번 실행되어도 결과가 같도록 코드를 고치는 것이 이 검사를 쓰는 목적입니다. 검사 항목은 React의 StrictMode 문서에 정리되어 있습니다.
속성
속성(props)은 부모 컴포넌트가 자식 컴포넌트에 데이터를 넘길 때 씁니다. properties의 줄임말이며 읽기 전용입니다. 자식은 받은 props를 직접 바꾸지 않아야 하고, 자바스크립트로는 객체의 속성을 바꿀 수 있더라도 React가 정한 규칙을 어기는 것이므로, 바꿔야 할 때는 부모가 넘겨준 콜백 함수를 호출해 부모에게 요청합니다.
function Item(props) {
// props 객체에서 필요한 속성들을 구조 분해 할당으로 가져올 수도 있음
const { name, price } = props;
return (
<li>
{name} - {price}원
</li>
);
}
export default Item;// Item 컴포넌트 사용 예시
import Item from './Item.jsx'; // Item 컴포넌트 임포트
import Counter from './Counter.jsx'; // Counter 컴포넌트 임포트
import './App.css';
function App() {
const products = [
{ id: 1, name: '노트북', price: 1200000 },
{ id: 2, name: '마우스', price: 30000 },
{ id: 3, name: '키보드', price: 80000 },
];
return (
<div className="App">
<header className="App-header">
<h1>쇼핑 목록</h1>
<ul>
{/* 배열을 map으로 순회해 Item 컴포넌트 렌더링 */}
{products.map(product => (
// key는 목록에서 React가 각 항목을 구별하는 값 (여기서는 product.id)
<Item key={product.id} name={product.name} price={product.price} />
))}
</ul>
<hr />
<Counter /> {/* Counter 컴포넌트 사용 */}
</header>
</div>
);
}
export default App;props는 데이터가 부모에서 자식으로 한 방향으로 흐르는 React의 핵심 원칙을 보여 줍니다. 목록을 map()으로 그릴 때 붙이는 key는 형제 항목을 구별하기 위한 React의 정보라서 Item에 일반 props로 전달되지 않습니다. 자식에서도 항목의 ID가 필요하면 id={product.id}를 따로 넘깁니다. 또 항목이 추가되거나 삭제되거나 순서가 바뀌어도 같은 항목은 같은 key를 유지해야 하므로, 배열의 인덱스보다 데이터의 고유한 ID를 씁니다.
상태를 어디에 둘까
컴포넌트와 JSX, 상태, props를 함께 볼 때는 데이터가 어디서 시작되어 어느 화면으로 이어지는지를 먼저 파악하면 좋습니다.
TREE
React 상태 소유권과 단방향 흐름
두 자식이 함께 쓰는 상태는 가장 가까운 공통 소유자에 두고, 값은 props로 내려보내며 사용자 의도는 callback과 event로 올려보낸다.
갱신은 SearchPage에서
자식이 callback을 호출하면 소유자의 핸들러가 상태를 갱신하고 새 props를 내려보낸다. 상태의 원본은 이 소유자가 관리한다.
- props: 소유자에서 자식으로
- callback/event: 자식에서 소유자로
상태를 양쪽 자식에 복제하지 않는다. 소유권은 한 곳에 두고 데이터와 의도의 방향을 분리하면 갱신 근거를 추적할 수 있다.
위 그림은 검색 페이지에서 상태를 둘 곳을 보여 줍니다. 검색어 입력란인 SearchInput과 결과 목록인 ResultList가 같은 검색어와 결과를 함께 써야 하므로, 두 자식의 가장 가까운 공통 부모인 SearchPage가 query와 results 상태를 갖습니다. 실선은 값이 부모에서 자식으로 내려가는 props이고, SearchInput은 query를, ResultList는 results를 받아 화면에 보여 줍니다.
점선은 자식에서 부모로 올라가는 사용자의 의도입니다. 입력란은 글자가 바뀌면 onQueryChange 콜백을, 목록은 항목을 고르면 onSelect(id) 이벤트를 호출하고, 부모의 핸들러가 상태를 갱신해 새 props를 다시 내려보냅니다. 그림 아래의 설명처럼 상태를 두 자식에 따로 복제하지 않고 한 곳에서만 관리하며, 값이 내려가는 방향과 의도가 올라가는 방향을 나누면 상태가 왜 바뀌었는지 추적하기 쉽습니다.
정리
- React는 화면이 어떻게 보여야 하는지 선언하면 필요한 DOM 변경을 대신 반영하는 컴포넌트 기반의 UI 라이브러리입니다.
- 입력 하나는 이벤트 핸들러, 상태 갱신 예약, 렌더, 커밋, 브라우저의 그리기 순서로 화면에 반영됩니다.
- JSX는 자바스크립트 안에 마크업을 쓰는 문법으로, 중괄호로 표현식을 넣고
className과 카멜 표기 속성을 쓰며 Vite가 변환해 줍니다. useState는 상태와 설정 함수를 돌려주며, 설정 함수는 지금 핸들러의 값을 바꾸지 않고 다음 렌더를 예약하므로 연속 갱신에는 갱신 함수를 씁니다.- props는 부모가 자식에게 넘기는 읽기 전용 값이고, 자식은 콜백으로 부모에게 변경을 요청하며,
key는 props로 전달되지 않습니다. - 여러 컴포넌트가 함께 쓰는 상태는 가장 가까운 공통 부모에 두고, 값은 props로 내리고 의도는 콜백으로 올립니다.
연습 문제
1. 같은 핸들러에서 두 번 갱신하기
count가 4인 렌더의 이벤트 핸들러 하나에서 다음 코드를 실행했습니다. 같은 클릭에서 두 호출을 마친 뒤 화면에 보일 값은 얼마일까요? 두 줄을 모두 갱신 함수로 바꾸면 어떻게 달라질까요?
setCount(count + 1);
setCount(count + 1);해설 보기
두 호출 모두 이 렌더의 값 4를 읽으므로, 둘 다 다음 값을 5로 바꾸라고 요청합니다. 그래서 다음 렌더의 값은 5입니다.
두 줄을 setCount(previous => previous + 1)로 바꾸면, 대기열에서 앞의 결과를 받아 4 → 5 → 6으로 계산하므로 6이 됩니다. 버튼을 두 번 따로 누르는 것과 한 핸들러에서 두 번 호출하는 것을 헷갈리지 않도록 주의하세요.
2. 검색어 상태의 위치
SearchInput에 입력한 검색어를 ResultList도 보여 줘야 합니다. 두 자식에 각각 query 상태를 만들면 어떤 문제가 생길까요? 상태는 어디에 둬야 할까요?
해설 보기
원본이 두 개가 되어 한쪽만 바뀌면, 입력란과 결과 목록의 검색어가 서로 달라질 수 있습니다. 두 자식의 가장 가까운 공통 부모인 SearchPage가 query를 갖고, 그 값을 props로 내려보내야 합니다.
입력란은 글자가 바뀔 때 onQueryChange 콜백을 호출해 부모에게 변경을 요청합니다. 검색어로 계산할 수 있는 필터 결과라면 상태를 하나 더 만들기 전에, 렌더 중에 계산해서 만들 수 있는지 먼저 확인하세요.
3. undefined인 key
목록 항목을 클릭할 때 Item 안에서 props.key를 읽었더니 undefined였습니다. 자식에서 항목의 ID 7을 받으려면 호출하는 부분을 어떻게 써야 할까요?
해설 보기
<Item key={7} id={7} name="마우스" price={30000} />처럼 id를 따로 넘깁니다. key는 React가 형제 항목을 구별하는 데만 쓰고 자식에게 전달하지 않으므로, 자식이 읽어야 하는 값은 일반 props로 넘겨야 합니다.
다음 글 상태 관리 라이브러리 소개에서는 애플리케이션이 커져 여러 컴포넌트가 상태를 함께 써야 할 때, Context와 상태 관리 라이브러리로 상태를 다루는 방법을 살펴봅니다.