0826 React : 상태 관리, 데이터 흐름, 동적 렌더링
✅ 1. 객체로 상태값 관리하기
- 관련 있는 여러 상태값들을 개별적인
useState로 선언하는 대신, 하나의 객체로 묶어 관리하면 코드를 더 간결하고 체계적으로 만들 수 있습니다. 특히 폼(Form)의 여러 입력 필드를 관리할 때 유용합니다.
➕ 상태 업데이트 시 주의사항
- 객체 상태를 업데이트할 때는 반드시 이전 상태를 복사한 후, 변경하려는 값만 덮어써야 합니다. 그렇지 않으면 다른 속성값들이 사라지게 됩니다. 이때 스프레드 문법(
...)을 사용하는 것이 일반적입니다.
import React, { useState } from 'react';
function ExpenseForm() {
const [userInput, setUserInput] = useState({
enteredTitle: '',
enteredAmount: '',
enteredDate: '',
});
const titleChangeHandler = (event) => {
setUserInput((prevState) => {
return { ...prevState, enteredTitle: event.target.value };
});
};
return (
<form>
<input
type="text"
value={userInput.enteredTitle}
onChange={titleChangeHandler}
/>
{}
</form>
);
}
✅ 2. 상향식 데이터 전달 (Lifting State Up)
- React의 데이터 흐름은 기본적으로 하향식(Top-Down)입니다 (부모 → 자식, via Props). 자식 컴포넌트에서 발생한 데이터를 부모 컴포넌트로 전달해야 할 때는, 부모가 데이터를 처리할 함수를 자식에게 Props로 전달하고, 자식은 그 함수를 호출하는 방식을 사용합니다.
➕ 처리 흐름
- [부모] 데이터를 받아 처리할 함수를 정의합니다. (
saveExpenseDataHandler)
- [부모] 해당 함수를 자식 컴포넌트에 Props로 전달합니다. (
onSaveExpenseData={...})
- [자식] Props로 전달받은 함수를 특정 이벤트(e.g., 폼 제출)가 발생했을 때 호출합니다. 이때 전달할 데이터를 인자로 넘깁니다. (
props.onSaveExpenseData(data))
- [부모] 함수가 호출되면서 자식의 데이터가 부모의 상태를 업데이트합니다.
function NewExpense(props) {
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
props.onAddExpense(expenseData);
};
return (
<div>
{}
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
}
function ExpenseForm(props) {
const submitHandler = (event) => {
event.preventDefault();
const expenseData = { };
props.onSaveExpenseData(expenseData);
};
return <form onSubmit={submitHandler}>{}</form>;
}
✅ 3. 동적 리스트 렌더링
- 배열에 담긴 데이터를 화면에 목록 형태로 렌더링할 때는 JavaScript의
map() 메서드를 사용합니다. map()은 배열의 각 요소를 순회하며, 각 요소를 JSX 엘리먼트로 변환한 새로운 배열을 반환합니다.
➕ key Prop의 중요성
map()을 사용하여 리스트를 렌더링할 때는, 각 엘리먼트에 key라는 특별한 Prop을 반드시 포함해야 합니다.
key의 역할: React가 리스트의 항목이 변경, 추가, 또는 삭제되었을 때 어떤 항목을 변경해야 하는지 효율적으로 식별하기 위해 사용됩니다.
- 좋은
key: 각 항목을 고유하게 식별할 수 있는 안정적인 값이어야 합니다. (e.g., 데이터의 id) 배열의 인덱스는 최후의 수단으로만 사용해야 합니다.
function Expenses(props) {
return (
<div>
{}
{props.items.map((expense) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
</div>
);
}
✅ 4. 조건부 렌더링
- 특정 조건(State, Props 등)에 따라 다른 UI를 보여주는 것을 의미합니다. 이를 통해 동적이고 상황에 맞는 UI를 만들 수 있습니다.
| 방법 | 사용 사례 | 예시 코드 |
|---|
| 삼항 연산자 | if-else와 같이 두 가지 경우 중 하나를 렌더링할 때 | {isEditing ? <EditForm /> : <DisplayView />} |
논리 && 연산자 | 특정 조건이 참일 때만 엘리먼트를 렌더링할 때 | {isLoggedIn && <UserProfile />} |
if문과 변수 | 렌더링 로직이 복잡하여 JSX 밖에서 처리해야 할 때 | let content = <p>No items found.</p>;
if (items.length > 0) { ... }
return <div>{content}</div>; |
function ExpensesList(props) {
if (props.items.length === 0) {
return <h2 className="expenses-list__fallback">Found no expenses.</h2>;
}
return (
<ul className="expenses-list">
{props.items.map((expense) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}
</ul>
);
}```
---
### 📌 요약
* 관련 있는 상태는 **하나의 객체**로 묶어 관리하되, 업데이트 시에는 **스프레드 문법(`...`)**으로 이전 상태를 보존해야 합니다.
* 자식에서 부모로 데이터를 전달할 때는, 부모가 **함수를 Props로 내려주고** 자식이 그 함수를 **호출**하는 **상향식 데이터 전달** 패턴을 사용합니다.
* 배열 데이터를 UI 목록으로 변환할 때는 **`map()`** 메서드를 사용하며, 각 항목에는 반드시 고유한 **`key` Prop**을 지정해야 합니다.
* 상황에 따라 다른 UI를 보여주기 위해 **삼항 연산자, `&&` 연산자, `if`문** 등 다양한 **조건부 렌더링** 기법을 활용합니다.