[React] State 끌어올리기
2022.12.06
💡 React 데이터의 흐름과 State 끌어올리기
📌 React 데이터의 흐름
React 개발 방식의 가장 큰 특징은 페이지 단위가 아닌, 컴포넌트 단위로 시작한다는 점이 가장 큰 특징이다.
페이지를 만들기 이전에 컴포넌트를 먼저 만들고 조립한다.
위의 그림에서 NewTweetForm과 SingleTweet은 같은 Twittler컴포넌트 안에 속해있다.
이런식으로 나누어진 이유는 무엇일까? 그것은 오늘 공부의 핵심이라고 생각하는 단일 책임 원칙에 따른 구분이다.
즉, 두 개의 자식 컴포넌트가 하나의 상태에 접근하고 할 때는 두 자식의 공통 부모 컴포넌트에 상태를 위치해야 한다.
여기서 잠시 복습할 포인트는
하나의 컴포넌트는 한 가지 일만 한다.
데이터는 위에서 아래로 흐른다.
컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 전달인자(argument) 혹은 속성(attributes)처럼 전달받을 수 있다.
즉 데이터를 전달하는 주체는 부모 컴포넌트가 된다. 이는 데이터 흐름이 하향식(top-down)임을 의미
그렇다면 만약 새로운 tweet이 있다고 했을 때 이는 어떤 컴포넌트로 이동해야 할까?
위의 그림에서 트윗
버튼을 눌렀을 때 새로운 트윗을 추가하려면 부모 컴포넌트에서의 상태가 하위 컴포넌트에 의해 변하는 것을 발견할 수 있다.
이는 역방향의 데이터 흐름이 아닌가? 맞다 버튼을 통한 이 액션은, 부모의 상태를 변화시켜야 한다.
단방향 데이터 흐름을 따르면서 어떻게 역방향을 해결할 수 있냐고 생각할 수 있다. 이를 해결하기 위한 방법이 아래에 설명되어 있다.
📌 State 끌어올리기
상태를 변경시키는 함수 ( Handler )를 하위 컴포넌트에 props로 전달해서 해결할 수 있다. 이는 마치 콜백 함수를 사용하는 방법과 비슷하다.
단방향 데이터 흐름이라는 원칙에 따라 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터의 형태나 타입이 무엇인지만 알수 있다.
즉, 데이터가 state로부터 왔는지, 하드코딩으로 입력된 내용인지 알지 못한다. 이러한 문제를 React는 다음과 같이 해결했다.
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다.
이것은 단방향 데이터 흐름의 원칙을 벗어나지 않는 해결 방법이다. 이것이 바로상태 끌어올리기
이다.
이 파트를 공부할 때 그 어떠한 100마디 말보단 실습을 통해 이해하는 것이 가장 빠르게 이해할 수 있었다.
👨🏻💻 예제 1
위 예제는 부모와 자식 컴포넌트가 하나씩 존재하는 트리 구조이다. 그리고, 상태를 변경시킬 수 있는 메서드가 존재한다고 생각해보자.
import React, { useState } from "react";
export default function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
// 1. 상태를 변경하는 함수 찾기
const handleChangeValue = () => {
setValue("보여줄게 완전히 달라진 값");
};
return ( // 2.handleButtonChange라는 적당한 props를 만들어주기
<div>
<div>값은 {value} 입니다</div>
<ChildComponent handleButtonChange={handleChangeValue}/>
</div>
);
}
// 3. props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게
function ChildComponent({handleButtonChange}) {
const handleClick = () => {
// 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
handleButtonChange() // 4. 콜백함수로 호출해주기
};
return <button onClick={handleClick}>값 변경</button>;
}
의사코드로 순서를 남겼지만 구체적으로 남겨보자면
1. 상태를 변경하는 함수를 찾아준다. 위 예제에서는 handleChangeValue
2. ChildComponet에 적당한 이름의 props값을 넣어준다. handleButtonChange={handleChangeValue}
3. <ChildComponent> 는 마치 고차 함수가 인자로 받은 함수를 실행하듯, props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 된다.
4. props로 전달받은 함수를 컴포넌트 내에서 실행할 수 있게 콜백함수로 호출해준다.
👨🏻💻 예제 2
두번째 예제는 다음과 같다.
위의 방법을 참고해서 이해하면 굉장히 빠르게 학습할 수 있다.
const addNewTweet = (newTweet) => {
setTweets([...tweets, newTweet]);
}; // 이 상태 변경 함수가 NewTweetForm에 의해 실행되어야 합니다.
// 1.상대 변경함수인 addNewTweet 자체를 하위 컴포넌트로 전달하고 하위 컴포넌트에서 이를 실행
// 2. 이를 실행할 하위 컴포넌트는 NewTweetForm
return (
<div>
<div>작성자: {currentUser}</div>
<NewTweetForm onButtonClick={addNewTweet}/>
<ul id="tweets">
{tweets.map((t) => (
<SingleTweet key={t.uuid} writer={t.writer} date={t.date}>
{t.content}
</SingleTweet>
))}
</ul>
</div>
);
} // 4. NewTweetForm 컴포넌트에 addNewTweet이 onButtonClick의 props로 적용
// 3. onButtonClick이라는 적당한 이름의 props가 적혀져 있는데 이것을 활용
function NewTweetForm({ onButtonClick }) {
const [newTweetContent, setNewTweetContent] = useState("");
const onTextChange = (e) => {
setNewTweetContent(e.target.value);
};
const onClickSubmit = () => {
let newTweet = {
uuid: Math.floor(Math.random() * 10000),
writer: currentUser,
date: new Date().toISOString().substring(0, 10),
content: newTweetContent
};
// TDOO: 여기서 newTweet이 addNewTweet에 전달되어야 합니다.
onButtonClick(newTweet)
};// 5. onButtonClick이 newTweet을 콜백함수로 받음
Comments