[React] 상태(state) 끌어올리기, useEffect 기본
🦋 시작하며
React의 데이터의 흐름과 어떤 것을 state로 두어야 하는지 이해해야한다. (링크)
🧐 상태 끌어올리기는 왜 필요할까
간단히 말하자면, 상위 컴포넌트의 state를 하위에서 바꾸어야 할때 필요하다.
"단방향 데이터 흐름"이기 때문에, 자식 컴포넌트 여러개가 하나의 상태에 접근하고자 할때
두 자식의 공통 부모 컴포넌트에 상태를 위치해야 하고 자식에게 props로 state를 물려줘야한다.
그런데 이 부모컴포넌트의 상태를 바꿔야하는 자식 컴포넌트가 있다면?
여러개의 자식 컴포넌트가 상태에 접근해 두기위해서 부모에 state는 그대로 두되,
특정 자식컴포넌트는 부모의 상태에 접근해 변경해줄 수 있도록 세팅해야 한다.
이 과정을 공식홈페이지에선 "역방향 데이터 흐름 추가"한다고 표현한다.
📌 상태 끌어올리기 예시
📌이모지가 있는 부분을 중점으로 보면 된다.
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>
//📌 부모컴포넌트에서 prop으로 state갱신함수를 포함하는 함수를 걸어줬다.
<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 }) { // 📌 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
}
// 📌 자식 컴포넌트에서 부모에서 걸어준 상태변경함수 포함 함수를 인자를 넣어 호출한다.
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;
마치 고차 함수가 인자로 받은 함수를 실행하듯, props로 전달받은 함수를 컴포넌트 내에서 이벤트가 발생하였을때 실행되도록 걸어준다.
☄️ Side Effect (부수 효과)
Side Effect란 어떤 구현이 함수 외부에 영향을 끼치는 효과를 말하며, 그런 함수를 Side Effect가 있다고 말한다.
side effect 예시 )
let foo = 'hello';
function bar() {
foo = 'world';
}
bar(); // 📌 bar는 외부의 foo를 변화시켰으므로 Side Effect가 있다.
🌟 Pure Function (순수 함수)
Pure Function이란 오직 함수의 입력만이 함수의 결과에 영향을 주는 함수를 의미한다.
좀 말이 어려운데 아래의 특징들로 알면 더 쉽다.
- 함수의 입력이 아닌 다른 값이 함수의 결과에 영향을 미치지 않는다.
- 입력으로 전달된 값을 수정하지 않는다.(immutable 메서드사용)
- 어떠한 전달 인자가 주어질 경우, 항상 똑같은 값이 리턴됨을 보장(예측 가능한 함수)
pure function 예시 )
function upper(str) {
return str.toUpperCase(); // 📌 toUpperCase 메소드는 원본을 수정하지 않는다 (Immutable)
}
upper('hello') // 'HELLO'
pure function Q&A)
Math.random()는 순수함수가 아니다. ➡️ ⭕️
Math.random()은 동일한 입력이 주어져도 다른 값을 반환하므로, 예측가능하지 않다. 그러므로 순수함수가 아니다.
fetch API를 이용해 AJAX 요청을 하는 함수는 순수 함수가 아니다. ➡️ ⭕️
네트워크 상황, 서버상태에 따라 응답이 달라지므로, 예측가능하지 않다. 그러므로 순수함수가 아니다.
☄️ React 컴포넌트에서의 Side Effect
그냥 props를 받아 JSX로 출력하는 경우 어떠한 side effect도 없는, 순수함수로 친다.
리액트에서의 순수함수
function SingleTweet({ writer, body, createdAt }) {
return <div>
<div>{writer}</div>
<div>{createdAt}</div>
<div>{body}</div>
</div>
}
하지만, 다음의 경우는 모두 React 입장에서 Side Effect에 속한다.
- 타이머 사용 (setTimeout)
- DOM요소 직접제어
- 데이터 가져오기 (fetch API, localStorage)
타이머 API나 localStorage는 React와 상과없는 API이므로,side effect이다.
원래 이런 것들은 함수 컴포넌트의 본문 안에서 허용이 되지 않는다.
하지만! React에서 이런 side effect를 다루기 하여 Effect Hook을 제공한다.
🛠 useEffect로 Side Effect 타이밍 제어하기
useEffect에 대해 알기에 앞서 먼저 ,react가 렌더링을 하는 경우들을 알아보자
- 컴포넌트 생성 후 처음 화면에 렌더링(표시)
- 컴포넌트에 새로운 props가 전달되며 렌더링
- 컴포넌트에 상태(state)가 바뀌며 렌더링
위의 경우에 react는 똑똑하게 알아서 rendering을 하게 되어있다.
그런데 이렇게 react랜더링이 자동으로 수행되고 제어할 수 없기 때문에,
중간에 Side Effect가 있을 경우 오류가 날 수 있다.
(다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문)
(훅을 사용하지 않고 네트워크 요청을 하면 그 동안에 페이지가 멈추거나 깜빡일 수 있다.)
그래서! 특정 렌더링이 된 이후에 이 Side Effect함수를 실행할 수 있도록
react에서 나온 Hook이 바로 useEffect 이다.
복잡하게 따지면 아주아주 뭔 내용이 많다. 저 clean up 단계에 관한 것이라던가...
그런건 일단 생략하고 필요시 최하단 참고자료를 열람하자!
🧐 useEffect 어떻게 쓸까?
useEffect(함수, [종속성1, 종속성2, ...])
= '종속성1'이나 '종속성2'값이 바뀌어 랜더링되면 그 랜더링이 끝나고 함수를 실행시켜줘
- 첫번째 인자: 함수(effects라고도 부름) ➡️ '특정 조건'이 렌더링되면 이 함수가 수행되게 한다.
- 두번째 인자: 배열(optional) ➡️ 렌더링에 기준이 될 '특정 조건'을 걸어준다.
'종속성 배열'이란 조건을 담는 배열이다.
"이 값이 변하면 함수를 실행시켜줘!" 라며 그 기준이 될 값들을 나열한다.
📌 useEffect 주요 주의사항
기본적인 주의사항은 다음과 같다.
- 두번째 인자를 작성하지 않으면 기본적으로 React는 매 렌더링 이후에 effects를 실행한다.(첫번째 렌더링 포함)
- 두번째 인자(배열)를 넣으면 어떤 값이 변경되었을 때만 실행되게 할 수 있다.
- 두번째 인자에 빈배열을 넣으면 컴포넌트가 처음 생성될 때만 함수(effects)가 실행된다. (참고)
🛑 웬만하면 종속성배열(두번째 인자)를 설정해줘야한다. 때에따라 무한루프에 빠지기도 한다.
예시는 다음과 같다.
//-----------1-----------
useEffect(() => {
console.log('렌더링되었어요!');
}); //매 렌더링때마다 시행된다. (엄청나게 많이 찍힘)
//-----------2-----------
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // count가 바뀔 때만 effect를 재실행된다.
//-----------3-----------
useEffect(() => {
console.log('컴포넌트가 처음으로 렌더링되었어요!');
}, []); // 컴포넌트가 처음 생성될 때만 effect가 실행된다.