리액트 16.8부터 추가된 Hook은 클래스 컴포넌트에서만 사용할 수 있던 state와 여러 lifecycle(생명주기) API 기능들을 연동하여 함수 컴포넌트에서도 사용할 수 있도록 해주는 함수다 (⇒ 클래스 컴포넌트에서는 동작하지 않음). Hook의 최대 장점 중 하나는 코드를 생명주기 메서드 단위가 아니라, 기능 단위로 분리할 수 있다는 것이다.
Hook을 사용할 때는 두 가지 규칙을 준수해야 한다.
- 최상위(at the top level)에서만 호출하기 (반복문, 조건문, 중첩된 함수 내에서 실행 금지)
- 리액트 함수 컴포넌트 내에서만 호출하기 (+ 커스텀 Hook 내부)
리액트의 가장 대표적인 내장 Hook으로는 useState
, useEffect
를 꼽을 수 있고, 이외에도 useReducer
, useMemo
등의 다양한 내장 Hook이 존재하며, 원하는 기능을 넣은 커스텀 훅을 만들어 사용할 수도 있다.
이번 글에서는 useEffect
에 대해 알아볼 것이다. useEffect
를 알기 위해서는 Side Effect와 Lifecycle(생명주기)에 대한 이해가 우선적으로 필요하다.
Side Effect는 함수 호출 시, 함수 내부가 아닌 외부에 영향을 미치는 것을 말한다.
let num = 0;
function increase(){
num++;
}
increase();
위 예제의 increase 함수가 하는 일은 외부 스코프에 있는 변수 num 값을 변경하는 것 뿐이다. 함수 스코프 내부의 어떤 값을 변경시키지도 않고, 반환하는 것도 없다. 이처럼 외부 상태를 변경시키는 것을 ‘Side Effect’라고 한다.
리액트에서는 컴포넌트가 화면에 렌더링된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 일컫는다. 데이터 가져오기, DOM 수동으로 조작하기 같은 것들이 해당된다.
Side Effect는 두 가지로 나눌 수 있다.
리액트는 컴포넌트 기반의 View를 중심으로 한 라이브러리이다. 그렇다 보니 각각의 컴포넌트에는 라이프사이클(수명 주기)이 존재한다. 컴포넌트의 수명은 보통 페이지에서 렌더링되기 전인 준비 과정에서 시작하여 페이지에서 사라질 때 끝난다.
라이프사이클은 위의 그림과 같이 세 가지 카테고리로 나눌 수 있다.
컴포넌트를 처음 렌더링할 때, 혹은 컴포넌트를 업데이트하기 전후로 어떤 작업을 처리해야 하거나, 불필요한 업데이트를 방지해야 할 때 라이프사이클 메서드를 사용할 수 있다. 라이프사이클 메서드는 클래스 컴포넌트에서만 사용 가능하고, 함수 컴포넌트에서는 Hook을 사용해야 한다.
라이프사이클 메서드의 종류로는 총 아홉 가지가 있는데, 우선은 useEffect와 가장 관련이 깊은 componentDidMount
, componentDidUpdate
, componentWillUnmount
에 대해 먼저 알아보았다.
컴포넌트를 만들고, 첫 렌더링을 마친 후 실행된다.
이 안에서 다른 자바스크립트 라이브러리 또는 프레임워크의 함수를 호출하거나, 이벤트 등록, 타이머 함수, 네트워크 요청 같은 비동기 작업을 처리하면 된다.
리렌더링을 완료한 후 실행된다.
업데이트가 끝난 직후이므로, DOM 관련 처리를 해도 무방하다.
컴포넌트를 DOM에서 제거할 때 실행된다.
componentDidMount
에서 등록한 이벤트가 있다면 여기서 제거 작업을 해야한다.
useEffect
는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Effect Hook이다. 함수 컴포넌트 내에서 Side Effects를 수행할 수 있게 해준다. useEffect
는 페이지가 처음 렌더링 되고 난 후, 무조건 한 번은 실행된다. 클래스 컴포넌트의 componentDidMount
, componentDidUpdate
, componentWillUnmount
를 하나로 통합한 API라고 볼 수 있다.
useEffect에서 사용되는 effect는 componentDidMount, componentDidUpdate와는 달리, 브라우저가 화면을 업데이트하는 것을 차단하지 않는다. (⇒ 애플리케이션의 반응성 향상)
useEffect
도 리액트가 제공하는 기능 중 하나이므로, import
로 불러와야 한다.
import React, { useEffect } from "react";
useEffect(callback[, deps])
useEffect
의 첫 번째 파라미터에는 콜백함수를, 옵셔널인 두 번째 파라미터에는 의존값이 들어있는 배열(deps)을 전달한다.
useEffect(callback, [])
두 번째 파라미터에 빈 배열을 전달하면, 컴포넌트가 화면에 맨 처음 렌더링 될 때(⇒ 마운트될 때)만 콜백 함수가 실행된다. 생명주기 메서드 componentDidMount
처럼 동작한다.
// Example
useEffect(() => {
console.log("mount");
}, [])
useEffect(callback)
두 번째 파라미터에 아무것도 전달하지 않으면, 컴포넌트가 마운트될 때와 업데이트될 때 콜백 함수가 실행된다. 생명주기 메서드 componentDidMount
와 componentDidUpdate
를 통합한 것처럼 동작한다.
// Example
useEffect(() => {
console.log("mount + update");
})
useEffect(callback, [...deps])
두 번째 파라미터에 검사하고자 하는 특정 값을 넣은 배열을 전달하면, 해당 값이 업데이트될 때만 콜백 함수가 실행된다. 배열 안에는 useState
를 통해 관리하고 있는 상태를 넣어줘도 되고, props로 전달받은 값을 넣어줘도 된다.
useEffect(() => { return cleanupFunc }[, deps]);
컴포넌트가 언마운트 되기 직전에 특정 작업을 수행해야 한다면, useEffect
함수의 첫 번째 파라미터로 전달되는 콜백함수에서 cleanup 함수를 return
해주면 된다. 컴포넌트가 언마운트될 때 깨끗이 정리해야 하는 코드들을 넣으면 된다. cleanup 함수는 생명주기 메서드 componentDidUnmount
와 같은 기능을 한다.
// Example
useEffect(() => {
console.log("mount");
return () => {
console.log("unmount");
};
}, [])
마찬가지로, deps 배열 안에 검사하고 싶은 값을 넣어주면 해당 값이 업데이트 되기 직전에도 cleanup 함수가 실행된다.
Hook은 클래스 컴포넌트에서만 사용할 수 있던 state와 여러 lifecycle(생명주기) API 기능들을 연동하여 함수 컴포넌트에서도 사용할 수 있도록 해주는 함수다. Hook을 사용하면 생명주기 메서드에 따라서가 아니라, 기능에 따라서 분리할 수 있다.
리액트 내장 Hook 중 하나인 useEffect
는 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 Effect Hook이다. 클래스 컴포넌트의 componentDidMount
, componentDidUpdate
, componentWillUnmount
를 하나로 통합한 API라고 볼 수 있다.
useEffect
의 첫 번째 파라미터에는 콜백함수를, 옵셔널인 두 번째 파라미터에는 의존성 배열(deps)을 전달한다.
+
참고
useEffect가 두 번 호출되는 이유는 StrictMode 때문이다.
참고 자료
React Hook, Lifecycle methods diagram
김민준, 『리액트를 다루는 기술』, 길벗
리액트 라이프사이클의 이해