코딩/React

useEffect를 이용한 커스텀훅을 만들어보자(feat. useAsyncEffect)

2워노 2024. 2. 29. 23:53

1. useAsyncEffect 

  • useAsyncEffect는 React Hooks중 하나인 useEffect를 통해 제작한 커스텀 훅입니다.
  • 기본적으로 useEffect를 비동기 작업에 사용 할 수 있게 확장한 커스텀 훅이라고 할 수 있습니다.
  • 이를 사용하면, useEffect 내에서 async/await를 사용하여 비동기 작업을 수행할 수 있습니다
  • (useAsyncEffect 코드)
import { useEffect, DependencyList } from "react"

export const useAsyncEffect = (asyncEffect: () => Promise<void>, deps?: DependencyList) => {
  useEffect(() => {
    const asyncEffectWrapper = async () => {
      await asyncEffect()
    }
    void asyncEffectWrapper()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps)
}

 

2. 코드 설명

  • useAsyncEffect는 두 개의 인자를 받습니다. 첫 번째 인자는 Promise를 반환하는 async 함수이고, 두 번째 인자는 DependencyList입니다. 두 번째 인자는 선택적으로 사용할 수 있으며, deps가 변경될 때마다 useEffect가 호출되도록 합니다.
  • useEffect 내부에서 asyncEffectWrapper라는 비동기 함수를 선언하고, 호출합니다. 이 함수는 asyncEffect 함수를 비동기로 실행합니다.
  • 'void asyncEffectWrapper()'는 'asyncEffectWrapper()'가 Promise를 반환하기 때문에 Promise를 처리하지 않는 경고를 방지하기 위해 사용됩니다.
  • 마지막으로, 'eslint-disable-next-line react-hooks/exhaustive-deps' 주석은 ESLint 경고를 무시하도록 하는 주석입니다. 이는 deps 배열 내부의 값들이 useEffect 내부에서 사용되지 않을 때 발생하는 경고를 방지합니다.
  • 이로써, useAsyncEffect를 사용하면 useEffect를 이용하여 비동기 작업을 더욱 효과적으로 처리할 수 있습니다.

 

3. 그냥 useEffect로 비동기 작업을 하면 되는거 아닌가?

  • React의 useEffect Hook은 순수 함수여야 합니다. 즉, useEffect 함수는 동기적으로 실행되며, 이외의 효과를 발생시키지 않아야 합니다.
  • 그러나 비동기 함수는 Promise를 반환하게 됩니다. 따라서 useEffect 내부에서 비동기 함수를 직접 사용하게 되면, useEffect가 Promise를 반환하게 되어 순수 함수가 아니게 됩니다.
useEffect(async () => {
  // ... 비동기 작업
}, []);
  • 위와 같이 작성하면, useEffect는 비동기 함수를 호출한 결과인 Promise를 반환하게 됩니다. 이는 useEffect의 설계 원칙에 위배됩니다.

 

4. 그렇다면 왜 useEffect가 순수함수여야만 할까?

  • 이는 useEffect의 클린업 메커니즘과 관련이 있습니다. useEffect는 선택적으로 클린업 함수를 반환할 수 있습니다. 이 클린업 함수는 useEffect가 재실행되기 전이나 컴포넌트가 언마운트될 때 호출됩니다.
useEffect(() => {
  // ... 효과를 발생시키는 코드

  return () => {
    // ... 클린업 코드
  };
}, []);
  • 위와 같이 useEffect가 클린업 함수를 반환하면, React는 이 함수를 기억하고 적절한 시점에 호출합니다. 그러나 useEffect가 Promise를 반환하게 되면, React는 이를 클린업 함수로 해석할 수 없으며, 이로 인해 예상치 못한 동작이 발생할 수 있습니다.
  • 따라서 useEffect 내부에서 비동기 작업을 처리하려면, 별도의 async 함수를 선언하고 이를 호출하는 방식을 사용해야 합니다. 이렇게 하면 useEffect는 여전히 순수 함수를 유지하면서 비동기 작업을 처리할 수 있습니다. (아래 예시 참조)

 

5. (예시) useEffect vs useAsyncEffect

✨ useEffect

import { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      setData(data);
    };

    fetchData();
  }, []);

  if (!data) {
    return <div>Loading...</div>;
  }

  return <div>{data}</div>;
}
  • useEffect에서는 비동기 함수를 직접 사용할 수 없으므로, 내부에 비동기 함수를 선언하고 호출하는 방식을 사용합니다.

✨useAsyncEffect

import { useState } from 'react';
import { useAsyncEffect } from './useAsyncEffect';

function MyComponent() {
  const [data, setData] = useState(null);

  useAsyncEffect(async () => {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    setData(data);
  }, []);

  if (!data) {
    return <div>Loading...</div>;
  }

  return <div>{data}</div>;
}
  • useAsyncEffect를 사용하면 useEffect 내부에서 별도로 비동기 함수를 선언하고 호출하는 과정 없이 바로 async 함수를 사용할 수 있습니다. 이를 통해 코드의 가독성이 향상되고, 비동기 작업을 더욱 직관적으로 처리할 수 있습니다.

6. 결론

  • useAsyncEffect는 useEffect를 비동기 작업에 적합하게 확장한 커스텀훅으로, 비동기 작업을 더욱 효율적이고 간결하게 처리할 수 있습니다!