[React] useEffect 개념 및 예제 코드

@eunjios·2023년 9월 27일
1
post-thumbnail

useEffect가 왜 필요할까?
useEffect 콜백 함수는 언제 실행될까?
cleanup 함수는 뭘까? 🤔


useEffect가 왜 필요할까?

useEffect는 side effect를 처리하기 위해 필요하다. 예를 들어, 특정 컴포넌트가 마운트 된 후 HTTP 요청을 보내야 하거나, 버튼을 눌렀을 때 어떤 값을 localStorage에 저장해야 하거나, 유저의 입력의 유효성을 처리해야 하는 등 다양한 상황에 필요하다.

React의 주기능은 UI를 렌더링하는 것이다. 유저의 클릭이나 입력으로 상태가 달라질 수 있고 보여줘야 하는 UI를 다시 렌더링 할 수 있다.

그러나 이 외에도 처리할 일들이 많다. 서버에서 데이터를 가져오거나, localStorage에 값을 저장하거나, 에러 처리 등 렌더링 외의 것을 side effect 라고 한다.


예시

useEffect의 동작 원리를 파악하기 위해 예시를 들어보자.

만약 MyComponent 라는 컴포넌트가 처음 마운트 될 때 REST API를 통해 데이터를 받아오고 싶다. 이 때 useEffect 훅을 사용하지 않는다면 어떤 일이 발생할까?

useEffect를 사용하지 않는다면?

import React from 'react';

const MyComponent = () => {
  const [data, setData] = useState([]);
  
  const fetchData = async () => {
    try {
      const response = await axios.get(API_URL);
      setData(response);
      console.log('FETCH DONE!!');
    } catch (error) {
      console.log(error);
    }
  };
  
  fetchData(); // useEffect 사용하지 않음
  
  return (
    <div>{data}</div>
  );
}

export default MyComponent;

콘솔창이 난리난 것을 확인할 수 있다. 프로그램을 종료하기 전까지 FETCH DONE!! 이 계속해서 찍힌다. 이는 useState 때문인데, HTTP 응답을 받아 setData를 실행하게 되면 data가 포함된 MyComponent가 다시 평가되고 실행되기 때문이다. 즉, 무한 루프에 빠지게 되는 것이다.

useEffect를 사용한다면?

import React from 'react';

const MyComponent = () => {
  const [data, setData] = useState([]);
  
  const fetchData = async () => {
    try {
      const response = await axios.get(API_URL);
      setData(response);
      console.log('FETCH DONE!!');
    } catch (error) {
      console.log(error);
    }
  };
  
  useEffect(() => { // useEffect 사용
    fetchData();
  }, []);
  
  return (
    <div>{data}</div>
  );
}

export default MyComponent;

이 경우는 FETCH DONE!!이 한 번만 찍힌다. 즉, MyComponent가 처음 마운트 될 때만 fetchData가 실행된다.


useEffect를 어떻게 쓸까?

useEffect를 언제 사용해야 하는지, 왜 필요한지에 대해 알아봤으니 어떻게 사용할지 알아보자.

useEffect(callback, [dependency]);

기본적인 문법은 위와 같다. 그러나 dependency 와 callback 함수의 모양에 따라 다르게 동작한다. 각 경우에 따라 callback 함수가 언제 동작하는지 알아보자.

useEffect 실행 순서

(1) dependency가 빈 배열일 때

callback 실행 시점
컴포넌트가 처음 마운트 될 때 한 번

useEffect(callback, []);

위 예시에서 사용한 방법이다. dependency 배열이 비어있을 때는 useEffect가 포함된 컴포넌트가 맨 처음 마운트될 때만 callback 함수가 실행된다.

(2) dependency가 빈 배열이 아닐 때

callback 실행 시점
컴포넌트가 처음 마운트 될 때 한 번
dependency가 달라질 때

useEffect(callback, [dependency]);

컴포넌트가 처음 마운트 될 때, 그리고 dependency에 포함된 state나 props 등이 달라질 때 callback 함수가 실행된다.

(3) dependency가 아예 없을 때

callback 실행 시점
컴포넌트가 처음 마운트 될 때
컴포넌트가 다시 렌더링 될 때

useEffect(callback);

두 번째 (dependency) 인자가 아예 없는 경우, 컴포넌트가 처음으로 마운트 될 때와 재렌더링 될 때 콜백 함수가 실행된다. useEffect에서 거의 쓰이지 않는 방법이다.

(4) callback에 return이 있을 때

반환하는 함수 실행 시점
effect 실행 전

useEffect(() => {
  // effect
  return (
    // cleanup
  );
}, [dependency]);

useEffect의 콜백 함수에 cleanup 함수를 반환하는 경우가 있다. 이 때도 dependency에 따라 cleanup 함수의 동작 시점이 달라진다.

dependency가 빈 배열이 아닐 경우는 다음과 같이 동작한다. 컴포넌트가 처음 마운트 될 때는 effect 부분만 실행된다. 마운트 된 이후에 dependency가 바뀔 때마다 cleanup 함수가 실행되고, 그 후에 effect 가 실행된다.

dependency가 빈 배열일 경우, 컴포넌트가 처음 마운트 될 때 effect가 실행된다. 이후 해당 컴포넌트가 언마운트되면 그 때 cleanup 함수가 실행된다.


예제 코드

입력한 텍스트가 10자 이상인지 유효성 검사하는 예제

  • textarea의 입력 값이 변경될 때마다 text 상태 업데이트
  • textarea의 입력 값의 유효성 검사는 사용자가 입력을 0.5초 이상 멈췄을 때에만 실행

useEffect와 cleanup 함수 사용하기

  • textarea의 변화를 onChange 로 감지하고 바뀔 때마다 textChangeHandler 를 실행
  • useEffecttext가 바뀔 때마다 유효성 검사
  • 0.5초 전에 text 가 업데이트 되면, 대기 상태였던 setTimeout 내부 코드를 무효화. 이를 위해 cleanup 함수를 사용하여 기존 setTimeout 을 무효화 함. (clearTimeout 사용)
import React, { useEffect, useState } from "react";
import Warn from "./Warn";

export default function App() {
  const [text, setText] = useState("");
  const [isValid, setIsValid] = useState(true);

  const textChangeHandler = (e) => {
    setText(e.target.value);
  };

  useEffect(() => {
    let timer;
    if(text.length === 0) {
      setIsValid(true);
    } else {
      timer = setTimeout(() => {
        console.log("[effect] 유효성 검사중..");
        setIsValid(text.trim().length >= 10);
      }, 500);
    }
    return () => {
      console.log("[cleanup]");
      clearTimeout(timer);
    };
  }, [text]);

  return (
    <React.Fragment>
      <form>
        <textarea
          name="text"
          value={text}
          type="text"
          placeholder="10자 이상 작성하세요"
          onChange={textChangeHandler}
          onBlur={() => setIsValid(true)}
        />
        <button type="button">확인</button>
      </form>
      {!isValid && <Warn />} // 경고 메시지
    </React.Fragment>
  );
}

결과 확인

demo

아래 콘솔창을 확인해보면 cleanup과 effect가 각각 언제 실행되는지 알 수 있다. cleanup은 입력 이벤트가 발생할 때마다 실행되는 반면, effect (유효성 검사)는 5번 밖에 실행되지 않았다.

왜 cleanup 함수를 반환해야 할까?

만약 return 하는 cleanup 함수가 없었다면 effect 실행 전에 clearTimeout 을 실행할 수 없다. 즉, setTimeout으로 0.5초 후에 실행되기로 한 코드 (유효성 검사) 는 0.5초 후에 실행될 뿐이다. 결국 유효성 검사가 0.5초씩 밀릴 뿐 모든 입력에 대해 유효성 검사를 하게 된다.

이 코드에서 유효성 검사는 매우 간단하지만, 만약 유효성 검사 절차가 까다롭다면 매 입력마다 유효성을 검사하는 방식은 매우 비효율적일 것이다. 따라서 useEffect 작업에서 cleanup 함수를 활용하는 것은 효율적인 작업에 필요하다.


참고 자료

profile
growth

0개의 댓글