위 그림처럼 State가 업데이트되는 해당 컴포넌트는 전체적으로 렌더링이 다시 일어나게됩니다.
import { useState } from "react";
const App = () => {
const [counter, setCounter] = useState(0);
const onClick = () => setCounter((cur) => cur + 1);
console.log("render");
console.log("api 요청")
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Add Counter</button>
</div>
)
}
이때 만약 해당 컴포넌트가 api 요청과 같은 많은 양의 데이터를 받아온다고 가정한다면 아래와 같습니다.
이런 경우를 방지하게 위해 리액트는 useEffect라는 기능을 제공합니다.
즉, state가 업데이트 되어도 useEffect 기능을 추가한 코드는 다시 실행되지 않게 만들 수 있습니다.
React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업을 이전에도 종종 해보셨을 것입니다. 우리는 이런 모든 동작을 “side effects”(또는 짧게 “effects”)라고 합니다. 왜냐하면 이것은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문입니다.
Effect Hook, 즉 useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해줍니다. React class의 componentDidMount 나 componentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합된 것입니다. - 출처: 리액트 공식문서
import { useEffect, useState } from "react";
const App = () => {
const [counter, setCounter] = useState(0);
const onClick = () => setCounter((cur) => cur + 1);
console.log("render");
useEffect(() => {
console.log("api 요청")
}, []);
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Add Counter</button>
</div>
)
}
export default App;
위와 같이 useEffect를 사용하면 첫번째 렌더링을 제외하고 state가 업데이트 되더라도 useEffect를 사용한 코드는 다시 실행되지 않습니다.
이것이 가능한 이유는 useEffect를 사용하면 React는 DOM을 바꾼 뒤에 effect 함수를 실행하기 때문입니다.
Effects는 컴포넌트 안에 선언되어있기 때문에 props와 state에 접근할 수 있습니다.
기본적으로 React는 첫 번째 렌더링도 포함해서 매 렌더링 이후에 effects를 실행합니다.
import { useEffect, useState } from "react";
const App = () => {
const [counter, setCounter] = useState(0);
const [keyword, setKeyword] = useState("");
const onChange = (event) => setKeyword(event.target.value);
const onClick = () => setCounter((cur) => cur + 1);
useEffect(() => {
console.log("api 요청")
}, []);
console.log("state 업데이트");
console.log("button 업데이트");
console.log("input 업데이트")
return (
<div>
<input value={keyword} onChange={onChange}/>
<div>{counter}</div>
<button onClick={onClick}>Add Counter</button>
</div>
)
}
export default App;
위와 같이 state가 두개 이상의 경우에도 문제가 있는데 counter
와 keyword
어느 하나의 state가 변경되어도 전체적으로 렌더링이 일어난다는 것이다.
Input에 3번의 입력과 button에 4번의 클릭이 있어 처음을 제외한 7번의 렌더링이 다시 일어났다.
이 또한 불필요한 렌더링이기 때문에 useEffect 기능을 사용해서 원하는 state의 변화만을 찾을 수 있다.
import { useEffect, useState } from "react";
const App = () => {
const [counter, setCounter] = useState(0);
const [keyword, setKeyword] = useState("");
const onChange = (event) => setKeyword(event.target.value);
const onClick = () => setCounter((cur) => cur + 1);
useEffect(() => {
console.log("api 요청")
}, []);
console.log("state 업데이트");
useEffect(() => {
console.log("button 업데이트");
}, [counter]);
useEffect(() => {
console.log("input 업데이트");
}, [keyword]);
return (
<div>
<input value={keyword} onChange={onChange}/>
<div>{counter}</div>
<button onClick={onClick}>Add Counter</button>
</div>
)
}
export default App;
똑같이 Input에 3번의 입력과 button에 4번의 클릭 결과이다.
useEffect를 사용하지 않은 state 업데이트
는 7번 호출이 됐고 useEffect를 사용한 각각의 3번의 input 입력
으로 input 업데이트는 3번 호출
, button 업데이트
는 4번 호출
된걸 확인할 수 있다.
useEffect(() => {
}, [])
위와 같이 빈배열[]
을 두번째 인자로 넣어준 경우 useEffect는 지켜볼 변화가 없기 때문에 1번만 실행하고
useEffect(() => {
}, [keyword])
위와 같이 [keyword]
state를 두번째 인자로 넣어준 경우 useEffect는 keyword state
의 변화를 관찰하여 업데이트가 될 경우 안에 함수를 실행한다고 생각할 수 있다.
추가로 if문 등을 활용해서 useEffect의 조건을 다양하게 만들 수도 있다.