React | useEffect Hook

seoltang·2022년 1월 17일
0

Side Effect

함수가 어떤 동작을 할 때, input - output 이외의 다른 값을 조작한다면, 이 함수에는 Side Effect(부수 효과)가 있다고 표현한다.

let num = 1;
function printNum(count) { // Input
  result = count + num; // Side Effect!
  result = count + 1; // Side Effect 아님!

  return `${name}님 안녕하세요!` // Output
}

이러한 Side Effect는 React의 함수 컴포넌트에서도 일어날 수 있다.

React에서 함수 컴포넌트의 rendering이란 state, props를 기반으로 UI 요소를 그려내는 행위이다. 즉 함수 컴포넌트의 Input이 state, props이고, output이 UI라고 할 수 있다. 그렇다면 함수 컴포넌트의 Side Effect는 state와 props를 받아서 UI를 그려내는 것 이외의 행위인 것이다.

즉, 함수 컴포넌트에서의 Side Effect는, 렌더링이 아니고 외부 세계에 영향을 주는 어떠한 행위이다. 대표적으로 Data Fetching, DOM에 직접 접근(ex. Event Listener 등록), 구독(ex. setInterval)과 같은 행위들이 있다. 이들은 모두 컴포넌트에서 꼭 필요한 대표적인 Side Effect들이다.

그러나 Side Effect들은 함수의 body 자리(render)에서 실행시키면 안 된다.

function greetWithSideEffect({ name }) { // 
  // Bad!
  document.title = `${name}님 안녕하세요!`; // Side Effect

  return <div>{`${name}님 안녕하세요!`}</div>; // Output
}

Side Effect들을 함수의 body 자리, 즉 return 전에서 실행시키면 2가지 문제가 발생한다.
1. 렌더링을 막는다. (block rendering)
Side Effect가 실행되는 데 30초가 걸린다고 하면, 렌더링은 30초가 지연되고, 사용자는 30초 동안 UI를 볼 수 없을 것이다.
2. 매번 실행된다. (always trigger)
서버에 데이터를 요청하는 작업을 매번 리렌더링을 할 때마다 실행한다면 굉장히 느리고 비효율적인 사이트가 될 것이다.

useEffect

useEffect(() => {}, [Dependancy Array])
useEffect는 첫 번째 인자로 콜백함수를, 두 번째 인자로 의존성 배열(Dependancy Array)을 받는다.

함수 컴포넌트의 렌더링은 기본적으로 아래 순서대로 일어난다.

  1. 컴포넌트가 렌더링된다. 최초로 진행되는 렌더링은 브라우저에 처음으로 이 컴포넌트가 보여졌다는 의미로 mount라고 표현한다.
  2. useEffect 첫 번째 인자로 넘겨준 함수(callback)가 실행된다(Side Effect).
  3. 다시 렌더링(re-render)이 일어난다(state나 props가 변경된 경우)
  4. useEffect는 두 번째 인자에 들어있는 의존성 배열을 체크한다.
    4-1. 만약 의존성 배열에 아무런 값도 넘기지 않았거나 / 의존성 배열에 들어있는 값 중 업데이트된 것이 하나라도 있다면 첫 번째 인자로 넘겨준 함수(callback)가 실행된다(Side Effect).
    4-2. 하나도 없거나 빈 배열이라면, 아무런 일도 하지 않는다.
  5. 만약 앞에서 일으킨 Effect에서 state나 props를 변경시켰다면 다시 렌더링이 일어난다.
  6. (중략...)
  7. 컴포넌트가 필요 없어지면 화면에서 사라진다. 컴포넌트가 브라우저의 화면에서 사라졌다는 의미로 unmount라고 표현한다.

useEffect의 인자로 넘겨준 콜백함수는 렌더링 이후에 실행된다.

import { useEffect } from 'react';

function greetWithSideEffect({ name }) { // Input
  useEffect(() => {
    // Good!
    document.title = `${name}님 안녕하세요!`; // Side Effect
  }, [name]);

  return <div>{`${name}님 안녕하세요!`}</div>; // Output
}

의존성 배열(Dependancy Array)에 따라 콜백함수가 실행되는 경우

  • 의존성 배열이 주어지지 않았을 때: 매 렌더링마다 실행
  • 의존성 배열이 빈 배열[]일 때: 최초 렌더링 이후 1번만 실행
  • 의존성 배열에 값이 있을 때: 값이 변경되면 실행
  • 의존성 배열에 값이 2개 이상 있을 때: 어느 하나의 값이라도 변경되면 실행

import { useEffect } from "react"

// 사용법
useEffect( 실행시킬 동작, [ 타이밍 ] )
document.addEventListener("타이밍", 실행시킬 동작) // 추상화한 예시

// 매 렌더링마다 Side Effect가 실행되어야 하는 경우
useEffect(() => {
  // Side Effect
})

// Side Effect가 첫 번째 렌더링 이후 한 번 실행되고,
// 이후 특정 값의 업데이트를 감지했을 때마다 실행되어야 하는 경우
useEffect(() => {
  // Side Effect
}, [value])

// Side Effect가 첫 번째 렌더링 이후 한 번 실행되고,
// 이후 어떤 값의 업데이트도 감지하지 않도록 해야 하는 경우
useEffect(() => {
  // Side Effect
}, [])

Clean up Effect

Cleanup Effect는 이전에 일으킨 Side Effect를 정리할 필요가 있을 때 사용한다.
Cleanup Effect는 useEffect의 콜백함수 안에서 return하면 된다.

useEffect(() => {
  return () => {
    // Clean up Effect
  };
}, []);

모든 useEffect 함수는 mount(마운트)될 때 실행되고, unmount(언마운트)될 때 모든 clean up 함수가 실행된다.

주의할 점은 단순히 컴포넌트가 생성되고, 사라지는 시점에만 Cleanup Effect가 실행되는 건 아니라는 것이다. 다음 Effect가 일어나기 전에, 이전 Effect의 영향을 정리해주는 의미에서 Cleanup Effect가 실행된다.

useEffect(() => {
  console.log("effect");

  return () => {
    console.log("clean up effect");
  };
});

이를 실행하면 콘솔창에는 clean up effect, effect 순서로 찍힌다.
즉 다음 Effect를 실행하기 전에 Cleanup Effect가 실행되는 것이다.

profile
공부 기록

0개의 댓글