[React] React 데이터 흐름
React 데이터 흐름
- bottom-up (상향식) 개발
React에서 개발 방식의 가장 큰 특징은 컴포넌트 단위로 나누어 개발한다는 것이다. 컴포넌트 단위를 찾고나면 각 컴포넌트를 만들고 조립하는 방식으로 웹 페이지 또는 앱을 만들어 나간다. 이 방식을 bottom-up(상향식) 개발 방식이라고 한다. 상향식 개발의 장점은 테스트가 쉽고 확장성이 좋다는 점이다. 이때 중요한 점은 하나의 컴포넌트가 한 가지의 기능만 가지도록 만드는 것이다. 그래야 이후 오류 발생시 오류가 발생한 컴포넌트를 쉽게 찾고 수정할 수 있다.
- Top-down (하향식) data flow
리액트를 대표하는 키워드 중 하나는 one-way data flow (단방향 데이터 흐름)이다. 컴포넌트는 컴포넌트 바깥에서 props를 통해 데이터를 전달받을 수 있다. 이때 데이터를 전달하는 주체는 부모 컴포넌트고 따라서 데이터의 흐름은 top-down (하향식) 방식이고, 자식 컴포넌트는 props를 통해 전달받은 데이터가 어디에서부터 전달되었는지 알지 못한다. 이것이 one-way data flow (단방향 데이터 흐름)이다.
State 끌어올리기 (Lifting State Up)
때로는 동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 필요가 있다. 이럴때 사용되는 것이 state 끌어올리기이다. 리액트 공식문서에는 다음과 같이 설명했다.
상위 컴포넌트의 “상태를 변경하는 함수” 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다
다음 예제를 살펴보자. 부모와 자식 컴포넌트가 트리구조 형태로 하나씩 존재하고 상태를 변경할 수 있는 메소드가 존재한다.
import React, { useState } from "react";
export default function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
const handleChangeValue = () => {
setValue("보여줄게 완전히 달라진 값");
};
return (
<div>
<div>값은 {value} 입니다</div>
<ChildComponent handleButtonClick={handleChangeValue} />
</div>
);
}
function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
handleButtonClick();
};
return <button onClick={handleClick}>값 변경</button>;
}
상태를 변경하는 함수 handleChangeValue
를 props로 자식 컴포넌트에 전달하고, 전달된 함수가 하위 컴포넌트에서 버튼클릭 이벤트handleButtonClick
에 따라 상태를 변경할 수 있게 됐다.
이처럼 하위 컴포넌트에서 이벤트를 통해 컴포넌트간 데이터를 전달할 수 있게 되는 것을 State 끌어올리기라고 한다.
필요에 따라 설정할 값을 콜백 함수의 인자로 넘길수도 있다.
function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
const handleChangeValue = (newValue) => {
setValue(newValue);
};
// ...생략...
}
function ChildComponent({ handleButtonClick }) {
const handleClick = () => {
handleButtonClick('넘겨줄게 자식이 원하는 값')
}
return (
<button onClick={handleClick}>값 변경</button>
)
}
다음 예제도 보면서 분석해보자
import React, { useState } from "react";
import "./styles.css";
const currentUser = "김코딩";
function Twittler() {
const [tweets, setTweets] = useState([
{
uuid: 1,
writer: "김코딩",
date: "2020-10-10",
content: "안녕 리액트",
},
{
uuid: 2,
writer: "박해커",
date: "2020-10-12",
content: "좋아 코드스테이츠!",
},
]);
const addNewTweet = (newTweet) => {
setTweets([...tweets, newTweet]);
};
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>
);
}
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,
};
onButtonClick(newTweet);
};
return (
<div id="writing-area">
<textarea id="new-tweet-content" onChange={onTextChange}></textarea>
<button id="submit-new-tweet" onClick={onClickSubmit}>
새 글 쓰기
</button>
</div>
);
}
function SingleTweet({ writer, date, children }) {
return (
<li className="tweet">
<div className="writer">{writer}</div>
<div className="date">{date}</div>
<div>{children}</div>
</li>
);
}
export default Twittler;
참고