📝 이 포스트는 useEffect in react: Everything you need to know를 참고한 글입니다!
React
로 개발할 때 DOM 업데이트 직후에 함수를 실행해야 할 필요가 많았습니다. 그럴때 우리는 Hooks
이전에는 ComponentDidMount
, ComponentDidUpdate
로 해결했었고, 현재는 useEffect를 사용하는 경우가 많습니다.
useEffect는 함수형 컴포넌트에서 side effects
들을 실행할 때 사용됩니다. 우리는 useEffect라는 Hook
을 이용해서 DOM 렌더링 직후에 컴포넌트가 할 일을 지시합니다. 많은 경우 네트워크 요청(데이터 요청) 또는 수동으로 DOM 변화를 지시합니다.
아래는 useEffect를 사용해 구현한 간단한 카운터입니다.
import {useEffect, useState} from "react";
export default function useEffectTest() {
const [count, setCount] = useState(0);
useEffect(()=> {
console.log("useEffect 실행! ",count);
});
console.log("렌더 이전 실행!");
const clickHandler = () => {
setCount(count + 1);
console.log("카운터 + 1 ",count);
}
return(
<div style={{textAlign:"center",marginTop:"10px"}}>
<h1>{count}</h1>
<button onClick={clickHandler}> + </button>
</div>
)
}
초기 렌더가 끝나면 console
에는 아래와 같은 문구가 출력되는 것을 확인 할 수 있습니다. 아직 +
버튼을 누르기 전입니다.
useEffect가 함수를 실행하기 전에 컴포넌트 안에 있는 렌더 이전 실행!
을 출력하는 함수가 먼저 실행됩니다. clickHandler
함수는 +
버튼을 누르기 전이므로 한번도 실행되지 않았습니다. 이제 한번 버튼을 눌러봅시다!
버튼을 한번 누르게 되면 위와 같은 순서로 console
창에 출력됩니다. 위와 같은 순서로 실행된 이유를 하나씩 봅시다.
onClick
의 결과로 clickHandler
가 실행되어 카운터 + 1 0
이 출력됩니다.
clickHandler
안에는 state를 변환하는 함수가 있지만 출력은 이전 state
인 0
을 보여줍니다. 이것은 함수 내부에서 state가 업데이트 되는 경우에 그 함수의 변환된 현재 값을 사용하기 위해서는 해당 함수 밖에서 사용해야 하기 때문입니다.
컴포넌트 전체가 실행되기 때문에 컴포넌트 내부에서 렌더 이전 실행!
을 출력합니다.
이 시점에서 View가 수정되어 화면의 숫자가 0에서 1로 변환됩니다.
DOM
이 업데이트 되었기 때문에 useEffect
가 실행됩니다.
이제 우리는 useEffect가 view의 렌더가 끝났을 때야 실행된다는 것을 이해했습니다. 그럼 왜 렌더가 끝났을때 실행되는 것 일까요?
렌더가 끝 마쳤을때야 비로소
state
가 변환되는 이유는?그 이유로는 사용자가
View
에만 신경 쓰기 때문이라고 합니다. 사용자가console
로그나,localStorage
와 같은side effect
들을 생각하지 않기 때문에 화면에서는 즉각적으로state
가 변하고 뒷단에서는 늦게 변환되어집니다.
state
변환과view
렌더 사이에 작업이 있으면 렌더가 느려지고 사용자가 부정적인 경험을 하게 됩니다.
useEffect를 사용할때 두번째 인자로 Dependancy Array
를 넣어주게 됩니다. Dependancy Array
는 이름처럼 useEffect가 의존성을 가질 상태 값들을 넣는 배열입니다. 아래의 표와 같은 값들이 들어갈 수 있습니다.
위의 표의 상황들을 하나씩 예로 보여드리겠습니다.
import {useEffect, useState} from "react";
export default function useEffectTest() {
const [input, setInput] = useState("");
const [label, setLabel] = useState("");
useEffect(()=> {
console.log("useEffect 실행! ");
});
return(
<div style={{textAlign:"center",marginTop:"10px"}}>
<h1>Input</h1>
<input onChange={ e => setInput(e.target.value) } style={{marginBottom:"10px"}}></input>
<h1>Buttons</h1>
<button onClick={() => setLabel("강아지")}>강아지</button>
<button onClick={() => setLabel("고양이")}>고양이</button>
<button onClick={() => setLabel("다람쥐")}>다람쥐</button>
</div>
)
}
Dependancy Array
를 사용하지 않았을 경우입니다. 이 경우에 모든 렌더와 모든 상태 값 변환마다 한번씩 useEffect안의 함수가 실행됩니다.
useEffect의 Dependancy Array
는 Optional
이기 때문에 사용할수도 사용하지 않을 수도, 빈 배열을 줄 수도 있습니다.
useEffect(()=> {
console.log("useEffect 실행! ");
}, []);
많은 경우 위와 같이 빈 배열을 주는데 이 경우는 최초 렌더 1회에만 useEffect 내부의 함수가 실행됩니다.
이번에는 Dependancy Array
에 값을 준 경우입니다. 위의 코드에서는 input
, label
state
가 2개 있습니다. 이 중에 특정 상태 값이 변경 될 때만 함수가 실행되는 useEffect를 만들 수 있습니다.
useEffect(()=> {
console.log("useEffect 실행! ");
}, [label]);
input
상태 값이 변경될 때는 useEffect 내부 함수가 실행되지 않는 것을 확인할 수 있습니다.
마지막으로 배열에 여러개의 state
들을 넣는 경우입니다. 이 경우에는 배열에 들어있는 모든 state
들에 의존성을 부여합니다.
useEffect(()=> {
console.log("useEffect 실행! ");
}, [label, input]);
이 경우는 맨 처음에 아무 값도 주지 않았을 때와 같은 모습을 보여줍니다. 하지만 이것은 해당 컴포넌트에 2개의 state
만 사용하기 때문이지 같다고 할 수는 없습니다.
useEffect를 사용할 때 많은 경우 API 호출을 합니다. 페이지에 진입 했을때 데이터를 불러오기 위해서 네트워크 통신을 렌더가 끝난 직후 실행해 데이터를 불러옵니다. 서버와의 통신은 비동기로 처리하기 때문에 useEffect 내부 함수를 async / await
로 처리하는데 이것은 잘못된 방식입니다.
😈 😈 😈 😈
useEffect( async () => {
await load();
},[])
useEffect와 같은 Hook
들은 함수를 반환해야하는데 async
를 사용할 시에 Promise
객체를 반환하게 됩니다.
그렇다고 useEffect로 네트워크 통신을 못하는 것은 아닙니다. 비동기 처리 함수를 따로 정의해주면 됩니다.
😇
useEffect( () => {
(async () => {
await load();
})();
},[])
위와 같이 비동기 처리를 하는 함수를 useEffect 함수 안에 정의하면 쉽게 원하는 동작을 할 수 있습니다.
좋은 글 잘 봤습니다~