..

Search

27) Effect Hooks

Effect Hooks


사이드 이펙트(side effects)

일부 React 컴포넌트는 화면에 표시되는 동안 네트워크, 브라우저 API 또는 다른 라이브러리와 연결된 상태를 유지해야 하는 경우가 발생할 수 있으며, 이렇게 React에 의해 제어되지 않는 시스템을 외부 시스템(external system)이라고 부릅니다.

 

대부분의 React 컴포넌트는 순수 함수(pure function)처럼 동작하며, 동일한 props를 인수로 전달 받으면 언제나 동일한 JSX 노드를 반환합니다. 반면에 함수 내의 코드가 실행되면서 함수 외부에 존재하는 값이나 상태 등을 변경시킴으로써 다른 곳에서 예기치 못한 결과를 발생시키는 경우도 있을 수 있습니다.

 

React에서는 이처럼 컴포넌트가 화면에 렌더링된 후에 외부 시스템과 연결된 상태에서 비동기적으로 처리되어야 하는 작업들을 사이드 이펙트 또는 이펙트(effect)라고 부릅니다. 이러한 사이드 이펙트는 외부 시스템과 함께 수행되기 때문에 그 결과를 정확히 예측할 수 없습니다.

 

대표적인 사이드 이펙트 작업은 다음과 같습니다.

- 컴포넌트의 DOM의 직접 수정

- 데이터를 가져오기 위한 외부 API 연동

- setTimeout(), setInterval() 등을 사용한 호출 스케쥴링


useEffect Hook

컴포넌트 내에서 이러한 사이드 이펙트를 직접 수행하는 것은 컴포넌트의 렌더링에 방해가 될 수 있습니다. 따라서 사이드 이펙트는 렌더링 과정과는 분리되는 것이 좋으며, 컴포넌트의 렌더링이 끝난 후에 처리되는 것이 바람직합니다.

 

실제 React에서는 특정 데이터를 가져오기 위해 외부 API 등을 호출하는 경우 화면에 렌더링할 수 있는 데이터를 먼저 모두 렌더링한 후에 실제 데이터는 비동기로 가져오는 것을 권장합니다.

 

useEffect는 클래스 컴포넌트에서 여러 생명 주기(life cycle) 메소드를 사용하여 복잡한 과정을 통해 처리했던 사이드 이펙트 로직을 하나의 Hook으로 손쉽게 처리할 수 있도록 해줍니다.

useEffect 문법
useEffect(setup, dependencies?)

 

useEffect의 첫 번째 인수는 setup 함수이며, 이 함수는 컴포넌트가 렌더링된 이후에 호출됩니다. setup 함수는 setup 코드를 통해 외부 시스템과 연결하고, 해당 시스템과의 연결을 종료할 수 있는 cleanup 함수(정리 함수)를 반환합니다.

두 번째 인수는 종속성 배열(dependency array)로 사이드 이펙트가 의존하는 모든 값을 포함하고 있는 배열입니다. React는 필요할 때마다 setup 및 cleanup 함수를 호출하며, 이 과정을 여러 번 반복할 수 있습니다.

 

useEffect는 렌더링 될 때마다 기본적으로 실행되지만, 두 번째 인수의 종류와 setup 함수의 반환문의 존재 여부에 따라 실행되는 조건이 달라집니다.


컴포넌트가 마운트 된 직후에만 실행

useEffect의 두 번째 인수로 전달되는 종속성 배열에 빈 배열을 전달하면, 컴포넌트가 가장 처음 렌더링 될 때만 setup 함수가 실행되고 이후 ref 값이 업데이트 될 때는 실행되지 않습니다. 이 형태는 생명 주기 메소드 중 componentDidMount의 동작과 동일합니다.

예제(App.js)
import React, { useState, useRef, useEffect } from "react";

const App = () => {
  const [state, setState] = useState("");
  // 호출 횟수를 체크하기 위한 ref 생성
  let ref = useRef(0);

  // 컴포넌트가 가장 처음 렌더링 될 때만 setup 함수 실행
  useEffect(() => {
    ref.current = ref.current + 1;
    console.log(`setup 함수 실행(${ref.current})`);
  }, []);

  const onChange = (e) => {
    setState(e.target.value);
  };

  return (
    <>
      ✔ 입력 필드 : &nbsp;
      {/* 입력 필드에 텍스트를 입력할 때마다 onChange 이벤트가 발생함 */}
      <input value={state} onChange={onChange} />
      <br />
      ️✔ 입력된 텍스트 : {state}
    </>
  );
};

export default App;

 

위 예제의 입력 필드에 텍스트를 입력해 봅시다. useEffect의 두 번째 인수로 빈 배열을 전달했기 때문에 ref의 값이 업데이트 되어도 더 이상 setup 함수를 호출하지 않는 것을 콘솔 창에서 확인할 수 있습니다.

 

console.log()는 웹 콘솔에 메시지를 출력하는 메소드로, 출력된 메시지는 콘솔(Console) 창에서 확인할 수 있습니다. 따라서 이번 장의 예제들은 모두 콘솔 창을 확인하여 계속적으로 출력 결과를 확인할 필요가 있습니다.

컴포넌트가 업데이트 될 때마다 실행

useEffect의 두 번째 인수로 전달되는 종속성 배열에 특정 props나 state가 저장되어 있다면, 컴포넌트가 가장 처음 렌더링 될 때와 해당 값이 변경될 때마다 setup 함수가 실행됩니다. 이 형태는 생명 주기 메소드 중 componentDidUpdate의 동작과 동일합니다.

 예제(App.js)
// state가 업데이트될 때마다 setup 함수 실행
  useEffect(() => {
    ref.current = ref.current + 1;
    console.log(`setup 함수 실행(${ref.current})`);
    console.log({ state });
  }, [state]);

 

이번에는 입력 필드에 텍스트를 입력할 때마다 setup 함수가 실행됨을 콘솔 창으로 확인할 수 있습니다.


컴포넌트가 언마운트 되기 직전에만 실행

useEffect의 첫 번째 인수로 전달되는 setup 함수가 cleanup 함수를 반환한다면, 컴포넌트가 언마운트 되기 직전에 해당 cleanup 함수가 실행됩니다. 이 형태는 생명 주기 메소드 중 componentWillUnmount의 동작과 동일합니다.

예제(App.js)
// 컴포넌트가 언마운트 되기 직전에 cleanup 함수를 실행함
  useEffect(() => {
    ref.current = ref.current + 1;
    console.log(`setup 함수 실행(${ref.current})`);
    console.log({ state });
    return () => {
      console.log(`cleanup 함수 실행(${ref.current})`);
    };
  }, [state]);

 

위의 예제에서는 입력 필드에 텍스트를 입력하여 ref 값이 업데이트 될 때마다 setup 함수와 cleanup 함수가 연속해서 실행되는 것을 확인할 수 있습니다.


연습문제