useEffect

dohun2·2023년 11월 29일

React

오늘은 React의 공식문서를 통해 useEffect를 사용하는 이유와 기본적인 동작흐름을 파악해보려고 한다!

useEffect 기본 사용법

import { useEffect } from 'react';

function Compoents() {

  useEffect(() => {
    console.log('부가 작업 실행!')
    
    return () => {
      console.log('클린 업 함수 실행!')
    };
  }, [state, props]);
  // ...
}
  • useEffect는 React의 Hook이므로 컴포넌트의 최상위 레벨에서 사용하여야 한다.
  • 조건문이나 반복문 내에서는 사용이 불가능하다.
  • useEffect는 클라이언트에서만 실행된다. => 서버에서 렌더링 시에는 실행되지 않는다.
  • useEffect는 렌더가 완료된 후에 실행된다.

Parameters

setup

  • 1번째 매개변수로 useEffect가 실행할 로직이 포함되어 있는 함수이다.

  • return 을 통해 클린 업 함수를 반환할 수 있다.

  • 클린 업 함수가 실행되는 타이밍

    • 컴포넌트가 DOM에서 제거된 이후 실행
    • dependencies에 넣은 값이 변경되어 리렌더링 될 때 실행
  • dependencies에 넣은 값이 변경되어 리렌더링 될 때에는 클린 업 함수가 먼저 실행되고 이후에 부가 작업 코드가 실행된다.

dependencies

  • props, state, 외 컴포넌트 내에서 선언된 모든 변수와 함수가 들어갈 수 있다.
  • Object.is 비교를 사용하여 각 의존성의 이전 값과 비교한다.

Return

  • useEffect의 반환 값은 undefined 이다.

실행순서

  1. 컴포넌트가 마운트 될 때 부가 작업을 하는 코드가 실행된다.
  2. 의존성이 변경된 컴포넌트를 리렌더링할 때 실행된다.
    2-1. 클린 업 함수가 있다면 클린 업 함수가 실행된다.(이전 상태값을 기준으로 실행)
    2-2. 부가 작업을 하는 코드 실행(현재 상태값을 기준으로 실행)
  3. 컴포넌트가 페이지에서 제거될 때 클린 업 함수 실행

예시코드

import { useEffect, useState } from "react";

const Count = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("렌더링!!", count);

    return () => {
      console.log("클린 업!!", count);
    };
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount((v) => v + 1)}>+</button>
    </div>
  );
};

export default Count;

위의 코드에서 count를 3까지 증가시킨 결과

  1. 마운트 될 때 부가 작업코드 실행 렌더링!! 0
  2. count가 1로 증가하고 다시 렌더링될 때 클린 업 함수 먼저 실행 클린 업!! 0
  3. 클린 업 함수가 실행된 후 부가 작업코드 실행 렌더링!! 1
  4. 2-3번 과정 반복

useEffect를 사용하는 이유와 장점

useEffect의 Parameters와 실행순서를 알아보면서 useEffect는 2번째 인자로 받은 의존성 배열에 등록된 state, props 혹은 컴포넌트 내에 선언된 변수나 함수의 값을 기억하고 있다가 Object.is 비교를 통해 값이 변화했다면 1번째 인자로 받은 콜백함수를 실행하는 것이라는 걸 알게되었다.

정리해보면 useEffect를 사용하는 이유는 특정 상태값이 변화했을 때 부수효과를 만들기 위함이라고 이해할 수 있을 것 같다.

또 의존성 배열과 실행타이밍을 이용하면 다음과 같은 장점도 있다.

  • 렌더가 완료된 후 실행되므로 렌더링 중 실행되는 컴포넌트 내에 선언된 함수와 달리 렌더링 속도와 연관이 없다.
  • 의존성 배열을 이용할 경우 상태에 따라 새롭게 선언되는 일을 방지할 수 있다.

dependencies 값에 따른 차이

위에서 dependencies에 의존성 배열을 넣었을 때의 동작을 알아보았는데 그렇다면 아무것도 넣지 않거나 빈 배열을 넣으면 어떻게 될까?

먼저 아무것도 넣지 않으면 렌더링 이후에 항상 실행된다.
그렇기 떄문에 보통은 렌더링이 된 걸 확인하기위해서 사용한다고 한다.

빈 배열을 넣으면 처음 마운트 될 때만 실행된다.
왜냐하면 의존성 배열은 있지만 의존성 값이 없어서 리액트가 비교할 게 없으니 항상 같다고 생각하기 때문이다. 이것을 이용하여 처음 렌더링 됐을 때 해야할 동작을 넣는데 많이 사용된다고 한다.

불필요한 의존성을 없애는 몇 가지 방법

다음으로는 공식문서에 소개되어 있는 불필요한 의존성을 개선하는 방법이다.

컴포넌트 내부에서 선언된 객체가 의존성 값으로 들어있는 경우

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const options = { 
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options); 
    connection.connect();
    return () => connection.disconnect();
  }, [options]); 
  // ...

해당 코드에서 useEffect의 의존성 값으로 option이 들어있는데 option이라는 객체는 컴포넌트 내에서 선언되고 있기 때문에 렌더링 시 항상 새롭게 만들어진다.
그렇게 되면 useEffect가 매 렌더링마다 실행되는 것이기 때문에 객체를 useEffect안에서 선언하는 방식으로 변경하여 의존성을 제거한다.

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

객체를 안에서 선언하면 roomId가 변경되었을 때만 부수 작업코드가 실행된다.

컴포넌트 내부에서 선언된 함수가 의존성 값으로 들어있는 경우

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  function createOptions() { 
    return {
      serverUrl: serverUrl,
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions(); 
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]);

해당 코드에서 createOptions 이라는 함수는 매 렌더링마다 새롭게 선언되고 해당 함수를 의존성값으로 가지고 있는 useEffect 또한 계속 실행된다.
함수가 문제가 되는 경우에도 객체와 똑같이 useEffect 안에서 선언하는 방법으로 해결할 수 있다.
느낌상 단순한 객체보다 조금은 복잡한 함수이기 때문에 더욱 최적화를 해야한다고 생각했는데 공식문서에 따르면 매 렌더링마다 함수가 새롭게 생성되는 것에 대한 최적화는 전혀 걱정할 필요가 없다고 한다...
하지만 useEffect가 매 렌더링마다 실행되는 것은 우리가 useEffect를 사용하는 이유가 될 수 없기 때문에 함수도 useEffect 안에서 선언해주도록 한다.

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    function createOptions() {
      return {
        serverUrl: serverUrl,
        roomId: roomId
      };
    }

    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

공식문서의 이후 부분은 트러블슈팅에 관한 내용으로 useEffect를 직접 사용해보면서 문제를 만나게 됐을 때 읽어보면 좋을 것 같다.

참고자료

https://react.dev/reference/react/useEffect

0개의 댓글