React의 대표적인 Hook인 useEffect는
함수형 컴포넌트에서 Side Effect를 처리하기 위해 사용한다.
💡 Side Effect
React 컴포넌트가 화면에 렌더링된 이후에 비동기로 처리되어야 하는 부수적인 효과
리액트의 함수형 컴포넌트는 화면에 보이는 내용을 결정하는 역할을 한다.
즉, props와 state에 따라 UI를 어떻게 그릴지 정의하게 된다!
그러나 어떤 작업들은 UI를 그리는 것과 직접적인 관련이 없는 경우가 있다.
예를 들어, 데이터를 서버에서 불러오거나 이벤트 리스너를 해제하는 등의 작업은
UI를 그리는 것과 상관 없이 비동기적으로 처리되어야 한다.
이러한 작업들이 바로 사이드 이펙트이다.
비동기를 비롯한 대표적인 Side Effect 예시는 아래와 같다.
렌더링과 무관한 연산이 컴포넌트 안에서 바로 실행되면 문제가 될 수 있다.
그 이유는 개발자가 컴포넌트의 렌더링 시점을 통제할 수 없기 때문이다 🚨
리액트가 내부적으로 최적화를 위해 컴포넌트를 여러 번 렌더링할 수 있는데
사이드 이펙트가 컴포넌트 렌더링 중에 발생하면 원치 않게 여러 번 실행될 수 있다.
그렇기 때문에 useEffect을 사용하여 사이드 이펙트가 렌더링 이후에 실행될 수 있도록 보장해야 한다!
useEffect 없이 사이드 이펙트를 실행하는 경우와
useEffect 안에서 사이드 이펙트를 실행하는 예시를 살펴보자!
function App() {
sideEffect();
return <div>App</div>;
}
sideEffect()
는 컴포넌트 렌더링 중에 실행된다.
컴포넌트가 렌더링 될 때마다 sideEffect()
가 즉시 실행되며,
렌더링 로직 안에서 함수가 실행되기 때문에 컴포넌트의 렌더링 성능에 영향을 미칠 수 있다.
만약 sideEffect()
함수가 비동기 작업을 포함하고 있다면
그 결과가 UI 업데이트 전에 도착할 수 있다.
function App() {
useEffect(() => {
sideEffect();
}); // 의존성 배열 생략
return <div>App</div>;
}
sideEffect()
는 컴포넌트가 렌더링된 후에 실행된다.
useEffect
안에서 Side Effect을 실행하는 경우 렌더링이 끝난 후에 실행되기 때문에
렌더링과 비동기 작업의 순서를 명확하게 관리할 수 있다!
결론 💬
Side Effect는 useEffect 같은 Effect Hook을 사용해서
컴포넌트가 렌더링된 이후에 처리하는 것이 올바르다!
useEffect는 컴포넌트가 렌더링 될 때마다 Side Effect 로직을 다루는 hook이다.
useEffect 등장 이전의 클래스 컴포넌트는 LifeCycle 메소드를 이용해 Side Effect을 처리하였다.
기존 클래스형 컴포넌트에서 렌더링과 관련된 생명 주기 메소드는 아래와 같다.
componentDidMount
: 컴포넌트가 마운트 될 때componentDidUpdate
: 컴포넌트가 업데이트 될 때componentWillUnmount
: 컴포넌트가 언마운트 될 때// Class Component
class Example extends React.Component {
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
}
위는 클래스형 컴포넌트에서 생명 주기 메소드를 사용하여 Side Effect를 처리하는 예시이다.
간단한 Side Effect 처리에도 복잡한 코드 작성이 필요하기 때문에 유지보수가 어렵다!
// Functional Component
const Example = () => {
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
}
이러한 문제를 해결하고자 useEffect가 등장하였다.
useEffect는 Side Effect를 LifeCycle이 아닌, 관심사에 따라 관리할 수 있게 한다.
관심사 분리는 컴포넌트에서 서로 다른 기능을 구분해 각자의 관심사를 가지도록 설계하는 원칙이다.
기존의 클래스형 컴포넌트에서는 componentDidMount
, componentDidUpdate
, componentWillUnmount
등 생명 주기 메소드가 각기 다른 시점에 사이드 이펙트를 실행하였다.
반면 useEffect는 사이드 이펙트 로직을 관심사에 따라 쉽게 나눌 수 있다.
아래는 클릭 횟수에 따라 페이지 제목을 업데이트하는 관심사와
초기 렌더링 시 콘솔에 메시지를 출력하는 관심사를 나누어 처리한 예시이다!
const Example = () => {
const [count, setCount] = useState(0);
// 클릭 횟수에 따라 페이지 제목을 업데이트하는 관심사
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
// 콘솔에 메시지를 출력하는 관심사
useEffect(() => {
console.log('컴포넌트가 렌더링되었습니다.');
}, []);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
};
useEffect의 첫 번째 인자는 렌더링 이후 실행할 사이드 이펙트 함수를 의미한다.
두 번째 인자인 deps는 의존성 배열 매개변수를 의미한다.
useEffect는 이 매개변수에 전달된 값을 토대로 Side Effect 로직을 실행한다.
useEffect(function, deps)
useEffect(() => {
console.log("컴포넌트가 렌더링 될 때 마다 실행");
})
배열을 생략하면 Side Effect 로직은 컴포넌트가 렌더링 될 때마다 실행된다.
모든 렌더링에 대응하기 때문에 의도와 다르게 Side Effect가 실행될 수 있다.
그렇기 때문에 되도록 deps을 비우는 것은 지양하는 것이 좋다!
useEffect(() => {
console.log("최초 렌더링 이후에 한번만 실행");
}, [])
deps에 빈 배열을 전달하면 Side Effect 로직은 컴포넌트 최초 렌더링 이후 한 번만 실행된다.
빈 배열은 주로 아래와 같은 상황에서 사용한다.
useEffect(() => {
console.log(name);
}, [name])
deps에 특정 props, state을 전달하면 Side Effect 로직은
컴포넌트 최초 렌더링 및 해당 값이 변경되어 리렌더링 될 때마다 실행된다.
useEffect(() => {
console.log("componentDidUpdate");
return () => {
console.log('clean up');
};
}, [name])
useEffect에 return문이 존재하면 이는 컴포넌트가 언마운트 됐을 때 실행되는 클린업 함수가 된다.
주로 아래와 같은 상황에서 사용한다.
잘못된 정보, 오탈자를 발견하면 편하게 댓글로 말씀해주시면 감사하겠습니다 🙂