리엑트에서 props라는 개념을 배울때 보통 부모컴포넌트(상위)에 있는 상태나 함수를 속성형태로 자식컴포넌트(하위)로 내려주어 데이터를 접근하고 관리 할 수 있다고 알고있다.
그렇지만 사용자로부터 데이터를 직접 받아야하는 부분이거나 동적으로 관리가 필요한 속성에 데이터를 자식에서 부모컴포넌트로 이동하기도 해야한다.
이를 "상태끌어올리기"(Lifting State Up) 라는 개념이라고 한다.
props를 사용해서 부모 컴포넌트로부터 함수를 받고 자식 컴포넌트에서 그 함수를 불러오는것이다.
앞서 적용했던 예제 코드의 플로우를 간단히 묘사한 컴포넌트 트리이다.
현재 Expenses컴포넌트와 NewExpense컴포넌트를 렌더링하는 App 컴포넌트가 있다.
여기서 NewExpense컴포넌트에서 사용자 입력을 받은 새로운 데이터를 생성하는 부분이 있고,
해당 추가된 데이터를 Expenses 컴포넌트에 필요에따라 데이터를 추가하는 등 적용하고 있다.
하지만 아쉽지만 리엑트에선 아래와같은 방식으로 동작하지 않는다.
왜냐하면 두 컴포넌트는 직접적으로 연결되어 있지 않기 때문이다.
리엑트는 부모와자식, 자식에서 부모로밖에 소통할 수 없다.
그렇기 때문에 가장 가까운 부모컴포넌트를 활용해서 간접적이든 직접적이든 관련이 있어야하는것이다.
위 컴포넌트의 가장 상위 컴포넌트인 App컴포넌트가 그 역할을 맡고 있고, 반환되는 JSX코드에서 두 컴포넌트를 모두 렌더링하기 때문에 Expense와 NewExpense에 모두 접근 가능하다.
따라서,
NewExpense에서 생성된 상태 데이터를 상태 끌어올리기를 해서 App컴포넌트에 전달할 수 있다.
그리고 나서 App컴포넌트에서 관장하는 데이터 목록을 통해 Expenses컴포넌트에서 전반적인 데이터를 렌더링 해줄 수 있도록 사용할 수 있는것이다.
컴포넌트 트리 그림상에서 NewExpense컴포넌트에서 내려진 props를 사용해 onAddExpense함수를 호출하여 적용하는데 중요한것은 호출하고 있는 함수에 ExpenseForm컴포넌트로부터 전달받은 데이터를 전달해야한다!
(사실 NewExpense컴포넌트에 전달된 데이터는 ExpenseForm으로부터 또한 사용자 입력 데이터에 따른 상태를 올려 받아 사용되고 있는것이다!)
function NewExpense(props) {
// 사용자 입력폼에서 받은 데이터를 저장할 수 있는 이벤트 함수 정의
// enteredExpenseData = ExpenseForm으로부터 submit헨들러에 입력된 객체 데이터
const saveExpenseDataHandler = (enteredExpenseData) => {
// 새롭게 추가된 입력 데이터를 저장할 객체 생성
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
props.onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
}
export default NewExpense;
위처럼 props.onAddExpense()로 호출된 함수의 인자에 생성한 객체인 expenseData를 전달하여 이렇게 해서 데이터 상태를 끌어올리는 것이다.
여기서 한가지 알아야할 것은 올려진 상태(expenseData)는 NewExpense가 아닌 App에서 관리하는것이다.
function App() {
const expenses = [
{
id: "e1",
date: new Date(2023, 5, 8),
title: "Car Insurance",
amount: 230.45,
},
{
id: "e2",
date: new Date(2022, 0, 12),
title: "Oil price",
amount: 630.44,
},
{
id: "e3",
date: new Date(2023, 9, 3),
title: "Toilet Paper",
amount: 110.45,
},
{
id: "e4",
date: new Date(2022, 11, 23),
title: "New Desk",
amount: 110.45,
},
];
// 새로운 입력된 비용데이터를 받아서 기존 expenses에 추가할 이벤트 함수 정의
const addExpenseHandler = (expense) => {
console.log("In App.js");
console.log(expense);
};
return (
<div>
<NewExpense onAddExpense={addExpenseHandler} />
<Expenses expenses={expenses} />
</div>
);
}
export default App;
추후 이 상태는 App컴포넌트 안에있는 데이터배열(expenses)안에서 항목처럼 동적으로 관리가 되어야하는 부분이고, 이 부분도 차차 정리를 해볼 예정이다.
따라서 App에 보관된 상태는 다시 props로 해당 state를 필요로하는 컴포넌트에 전달되는것이다.
항상 상태를 App컴포넌트까지 올려야 하는것은 아니고,
컴포넌트 트리상
데이터를 생성하는 컴포넌트와 데이터가 필요한 컴포넌트에 접근할 수 있으면 그정도만 끌어올려도 무방하다.