Chapter1. React 데이터 흐름
1-1 React 데이터 흐름
1-2 State 끌어올리기 (Lifting State Up)Chapter2. Effect Hook
2-1 Side Effect
2-2 Effect Hook 기본
2-3 Effect Hook 조건부 실행
2-4 컴포넌트 내에서의 Ajax 요청
1. 단방향 데이터 흐름(one-way data flow)
- 먼저 컴포넌트 단위로 만들고, 페이지를 조립하는 상향식(bottom-up)으로 앱 제작 : 테스트 쉽고, 확장성 좋음
- 컴포넌트는 컴포넌트 바깥에서 props를 이용해 데이터를 마치 전달인자(arguments) 혹은 속성(attributes)처럼 전달받을 수 있다.
- 즉, 데이터 흐름이 하향식(top-down)
- 데이터를 전달하는 주체 : 부모 컴포넌트.
상태가 특정 컴포넌트에서만 유의미하다 = 특정 컴포넌트에만 두면 됨
만약 하나의 상태를 기반으로 2개의 컴포넌트가 영향을 받는다 = 공통 소유 컴포넌트를 찾아 그곳에 상태를 위치해야함.
즉, 2자식 컴포넌트가 하나의 상태에 접근하려면 2자식의 공통 부모 컴포넌트에 상태를 위치해야함
2. 역방향 데이터 흐름 추가
아깐 단방향이라면서?
-> 상태 위치를 정하고 나니, 부모 컴포넌트의 상태가 하위 컴포넌트에 의해 변하게 됨.
-> 이를 해결하는 방법 "State 끌어올리기"
-> 상태를 변경시키는 함수를 하위 컴포넌트에 props로 전달해서 해결한다. 마치 콜백함수와 비슷
💡정리
데이터를 전달하는 주체 = 부모 컴포넌트
리액트의 데이터 흐름 = 위에서 아래로 흐르는 하향식
단방향 데이터 흐름으로 아래에서 위로는 전달할 수 없다.
자식 컴포넌트에 의해 부모 컴포넌트의 state가 바뀌는 경우를 해결하는 역할
단방향 데이터 흐름에 따라 하위 컴포넌트는 상위 컴포넌트로부터 전달받은 데이터 형태에 대해서만 알 수 있다. 그게 state로부터 왔는지에 관한 출처는 알 수 없다.
그러므로 하위 컴포넌트에 의해 상위 컴포넌트의 상태가 바뀌는 건 이해가 안 될 수 있다. React는 다음과 같은 해결책을 제시한다
상위 컴포넌트의 "상태를 변경하는 함수" 그 자체를 하위 컴포넌트로 전달하고, 이 함수를 하위 컴포넌트가 실행한다.
import React, { useState } from "react";
export default function ParentComponent() {
const [value, setValue] = useState("날 바꿔줘!");
const handleChangeValue = () => {
setValue("보여줄게 완전히 달라진 값");
};
return (
<div>
<div>값은 {value} 입니다</div>
// handleChangeValue() 함수를 변수에 담아서 하위 컴포넌트에 보내줘야 한다.
<ChildComponent handleBtnClick={handleChangeValue}/>
</div>
);
}
// handleChangeButton 잘 넣어주고
function ChildComponent({handleButtonClick}) // 그 후 하위 컴포넌트에서는 함수를 설정한 변수명으로 받아야 한다.
{
const handleClick = () => {
// 이 버튼을 눌러서 부모의 상태를 바꿀 순 없을까?
// 마지막으로 받아온 함수를 실행하기 위해 인자로 받은 상태 변경 함수를 실행하자!
handleButtonClick();
};
return <button onClick={handleClick}>값 변경</button>;
}
function Twittler() { // 최상위 컴포넌트
const [tweets, setTweets] = useState([
{생략}
]);
const addNewTweet = (newTweet) => { // 하위 컴포넌트가 상위 컴포넌트에 영향을 주는 변경함수
setTweets([...tweets, newTweet]);
}; // 이 상태 변경 함수가 NewTweetForm에 의해 실행되어야 합니다.
return (
<div>
<div>작성자: {currentUser}</div>
<NewTweetForm onButtonClick={addNewTweet}/> // 하위 컴포넌트에 props를 활용하여 변경함수를 전달.
<ul id="tweets">
{tweets.map((t) => (
<SingleTweet key={t.uuid} writer={t.writer} date={t.date}>
{t.content}
</SingleTweet>
))}
</ul>
</div>
);
}
function NewTweetForm({ onButtonClick }) { //하위 컴포넌트에 props를 활용하여 변경함수를 전달.
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) // step4 변경함수 활용해서 상태 끌어 올리기
};
export default Twittler;
💡정리
React 컴포넌트 외부에서 데이터를 처리하고 받아오는 과정
함수 내에서 어떤 구현이 함수 외부에 영향을 끼치는 경우.
React에선 컴포넌트 내에서 fetch를 사용해 API 정보를 가져오거나 이벤트를 활용해 DOM 직접 조작할 때 Side Effect가 발생했다고 함.
React 컴포넌트에서의 Side Effect
//전역 변수 foo를 bar라는 함수가 수정하는 예제
let foo = 'hello';
function bar() {
foo = 'world';
}
bar(); // bar는 Side Effect를 발생
오직 함수의 입력만이 함수의 결과에 영향을 주는 함수.
입력으로 전달된 값을 수정하지 않는다. 예측 가능한 함수
function upper(str) {
return str.toUpperCase(); // toUpperCase 메소드는 원본을 수정X(Immutable)
}
upper('hello') // 'HELLO'
useEffect(함수)
useEffect 첫번째 인자: 함수, 두번째 인자: 배열
: 컴포넌트 내에서 Side effect를 실행할 수 있게 하는 Hook.
이 컴포넌트에서 실행하는 Side effect는 브라우저 API를 이용하여 타이틀을 변경한다.
다음과 같은 조건에서 실행됨.
매번 새롭게 컴포넌트가 렌더링 될 때 Effect Hook이 실행됨
useEffect(함수, [종속성1, 종속성2, ...])
useEffect의 두 번째 인자는 종속성 배열(Dependency Array)
배열 내의 종속성1, 또는 종속성2의 값이 변할 때, 첫 번째 인자의 함수가 실행된다.
배열 내의 어떤 값이 변할 때에만, (effect가 발생하는) 함수가 실행된다.
빈 배열 넣기
useEffect(함수, [])
외부 API를 통해 리소스를 받아오고 더 이상 API 호출이 불필요할 때 사용
아무것도 넣지 않기 (기본 형태)
useEffect(함수)
컴포넌트가 처음 생성되거나, props가 업데이트 되거나, 상태(state)가 업데이트될 때 effect 함수 실행됨
useEffect(() => {
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
});
}, [filter]);
외부 API 접속이 느릴 경우를 고려해 로딩화면 구현은 필수적.
여기에도 상태 처리가 필요하다.
const [isLoading, setIsLoading] = useState(true);
// 생략, LoadingIndicator 컴포넌트는 별도로 구현했음을 가정합니다
return {isLoading ? <LoadingIndicator /> : <div>로딩 완료 화면</div>}
fetch 요청의 전후로 setIsLoading
을 설정하여 보다 나은 UX를 구현할 수 있다.
useEffect(() => {
setIsLoading(true);
fetch(`http://서버주소/proverbs?q=${filter}`)
.then(resp => resp.json())
.then(result => {
setProverbs(result);
setIsLoading(false);
});
}, [filter]);
💡
서버로 네트워크 요청을 보내야 되는 경우 = Ajax 요청 처리
이 때 Side Effect를 최소화 하기 위해 Effect Hook을 사용한다. 만약 훅을 사용하지 않고 네트워크 요청할 경우 페이지가 제대로 작동하지 않을 수 있다.