본 포스팅은 React Hook에서 제공하는 useEffect() 함수에서 async 함수를 사용하는 방법을 소개합니다.


개요

일반적으로 React에서 useEffect() 함수는 컴포넌트에서 부수 효과(side effect)를 다루기 위해 사용된다. 부수 효과(side effect)란 외부와 상호작용하거나 state를 변경하는 작업을 의미한다. 부수 효과는 다음과 같은 작업을 포함한다.

  1. 데이터 가져오기: 외부 API 호출을 통해 데이터를 가져오는 작업이 부수 효과의 일반적인 예시다.
  2. 상태 변경: 로컬 스토리지, 세션 스토리지에 데이터를 저장하거나 삭제하는 작업도 부수 효과에 포함된다.
  3. 타이머 설정: 타이머를 설정하여 일정 시간이 경과하면 특정 작업을 실행하는 것도 부수 효과에 포함된다.

API 호출을 통해 데이터를 가져와야하는 경우 async 함수를 사용할 수 있지만, useEffect() 함수에서 async 함수를 사용하는 것이 생각만큼 간단하지 않을 수 있다.

useEffect() 함수의 첫 번째 매개변수인 콜백 함수에 async/await 키워드를 사용하면 예상치 못한 동작이나 불필요한 렌더링을 초래할 수 있다. 문제가 발생하는 원인은 useEffect() 함수의 콜백 함수는 아무것도 반환하지 않거나 clean-up 함수를 반환하는데 async 함수는 Promise 객체를 반환하기 때문이다.

예시 1. 아무것도 반환하지 않음

다음과 같이 useEffect() 함수의 콜백 함수에 return 문을 작성하지 않은 경우 아무것도 반환하지 않는다.

useEffect(() => {
  // 부수 효과 로직 작성
}, []);

예시 2. clean-up 함수를 return

다음과 같이 useEffect() 함수의 콜백 함수에서 clean-up 함수를 return하는 경우 컴포넌트가 언마운트될 때, clean-up 함수가 실행된다.

useEffect(() => {
  // 부수 효과 로직 작성
  
  return () => {
    // 정리(clean-up) 로직
  }
}, []);

예시 3. clean-up 함수가 아닌 값을 return

만약에 다음과 같이 clean-up 함수가 아닌 값을 반환하면 TypeError: destroy is not a function이 발생한다.

useEffect(() => {
  return 123;
}, []);

예시 4. async 키워드 사용

다음과 같이 콜백 함수에 async 키워드를 사용하여 API를 호출하는 경우 빈 값, clean-up 함수가 아닌 Promise 객체를 반환하므로 TypeError: destroy is not a function이 발생한다.

useEffect(async () => {
  const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
  console.log(data);
}, []);

useEffect() 함수에서 async 함수 사용

useEffect() 함수에서 async 함수를 사용하려면 콜백 함수 내부에서 async 함수를 선언하고 호출한다.

useEffect(() => {
  // async 함수 선언
  const fetchData = async () => {
    const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const json = await data.json();
    console.log(json);
  };

  // async 함수 호출
  fetchData();
}, []);

한 가지 문제점은 async 함수인 fetchData()의 반환 결과를 fetchData() 함수 외부에서 사용할 수 없다는 것이다. 다음과 같이 fetchData() 함수에서 API 호출 결과를 json 형태로 return 했지만, 콘솔에는 Promise 객체가 출력된다는 것이다.

useEffect(() => {
  // async 함수 선언
  const fetchData = async () => {
    const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const json = await data.json();
    return json;
  };

  // async 함수 호출
  const result = fetchData();
  // fetchData() 함수의 return 값 출력
  console.log(result);
  // Promise {<pending>}
}, []);

따라서, API 호출 후 응답 결과를 접근하려면 다음과 같이 async 함수 내부에서 사용한다.

useEffect(() => {
  // async 함수 선언
  const fetchData = async () => {
    const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const json = await data.json();

    // API 호출 결과 사용(접근)
    setResult(json);
  };

  // async 함수 호출
  fetchData();
}, []);

즉시 실행 함수(IIFE) 사용

위 예시와 동일하게 동작하는 즉시 실행 함수(IIFE)를 사용하여 useEffect() 함수 내부에서 async 함수를 호출 할 수 있다.

useEffect(() => {
  // async 함수 선언
  (async () => {
    const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
    const json = await data.json();

    // API 호출 결과 사용(접근)
    setResult(json);
  })(); // async 함수 호출
}, []);

useEffect() 외부에 async 함수 정의

useEffect() 함수 외부에 async 함수를 정의하고 useEffect()에서 호출할 수 있다.

const fetchData = async () => {
  const data = await fetch("https://jsonplaceholder.typicode.com/todos/1");
  const json = await data.json();

  setResult(json);
};

useEffect(() => {
  fetchData();
}, []);

Concolusion

결론적으로 useEffect() 함수의 첫 번째 매개변수인 콜백 함수는 비동기로 동작할 수 있지만, 아무것도 반환하지 않거나 clean-up 함수를 반환해야한다. 따라서 Promise 객체를 반환하는 async 함수로 호출할 수 없다.

만약 useEffect() 함수에서 비동기 작업을 수행해야 한다면, 첫 번째 매개변수 내에서 async 함수를 호출하는 방식으로 작업을 수행할 수 있다.


참고

  1. How to use async functions in useEffect(with examples)
  2. How to go async with useEffect
  3. async-await function in useEffect

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다