언어 및 도구들 공부/React

useEffect()의 콜백함수 안에 async가 붙은 익명+즉시실행함수

Dr.Mlem 2024. 12. 3. 16:31
Q1.
useEffect(()=> {
        // Ajax 처리를 위한 비동기 구조. Hook에 연결되는 함수는 async를 직접 적용할 수 없기에, 즉시실행함수로 감싸준다.
        (async() => {       // (async()=> {비동기 작업 처리할 내용...})() --> 이렇게 즉시실행함수로 만들어서 useEffect의 리턴값에 넣어주면, async를 적용할 수 있다.
            let data=null;

            setLoading(true); // 로딩 상태임을 표시

            try{
                data=await axiosHelper.get("/titanic");
                console.log(data);
            }catch(e){
                alert(e.message);
                return; // 에러 생기면 중단.
            } 

            setTitanicData(data.item); // 받은 데이터를 상태변수에 적용
            setLoading(false); // 로딩 상태 종료
        })(); // 즉시 실행 함수꼴로 만들어 주기 위하여, ()를 뒤에 붙여준다.
    },[]);
 

이것은 결국, 이 부분에서, useEffect(()=> {안의 내용물}, []); 꼴인 것이고, {안의 내용물} 에서 안의 내용물을 감싼 {} 이 부분이, useEffect의 리턴하는 내용인거지?

 
 
 
A1.

ChatGPT의 말:

 

네, 맞습니다!

useEffect는 React에서 컴포넌트가 렌더링되거나 상태가 업데이트될 때 실행되는 사이드 이펙트를 관리하기 위한 Hook입니다. 질문하신 코드의 구조를 살펴보면, useEffect가 호출될 때 실행되는 내용물이 {안의 내용물}에 해당합니다.

다만, 정확히 말하자면 useEffect의 콜백 함수(즉, 첫 번째 인자로 넘긴 함수)가 실행되는 것이지, useEffect 자체가 {안의 내용물}을 리턴하지는 않습니다. 대신 useEffect의 콜백 함수는 클린업 함수를 리턴할 수 있습니다. 즉, 다음 구조를 따르게 됩니다:

 

코드에서의 주요 구조

질문 코드의 주요 구조를 다시 살펴보면:

1. useEffect의 콜백 함수 안의 내용물:

(() => { ... })();

 

여기에 비동기 처리를 위해 즉시 실행 함수(IIFE)를 작성했습니다. useEffect의 콜백 함수는 async로 직접 선언할 수 없으므로, 내부에 IIFE를 만들어 비동기 로직을 감싸 처리한 것입니다.

 

2. 리턴값: 이 코드의 경우, useEffect 내부에서 클린업 함수를 리턴하지 않았습니다. 즉, 리턴값은 undefined입니다.

정리

  • 질문 코드에서 useEffect의 {안의 내용물}은 useEffect가 실행할 콜백 함수로 전달됩니다.
  • 즉시 실행 함수(IIFE)를 사용하는 이유는 useEffect의 콜백을 async로 사용할 수 없기 때문입니다.
  • 이 코드에서 useEffect의 리턴값은 undefined이지만, 필요 시 클린업 함수를 리턴할 수 있습니다.

 

 
 
Q2.

나의 말:

즉, ()=> {안의 내용물}이 콜백함수이고, useEffect는 해당 콜백함수를 실행시킬 뿐이란거지?

 
 
 
A2.

ChatGPT의 말:

 

네, 정확합니다!

useEffect는 전달받은 콜백 함수를 실행시키는 역할을 할 뿐입니다. 따라서 useEffect의 첫 번째 인자로 전달된 함수 ()=> {안의 내용물}이 콜백 함수가 됩니다.

useEffect의 동작 흐름

1. 콜백 함수 전달:

useEffect(() => {
    // 이 부분이 콜백 함수
}, []);

여기서 ()=> {안의 내용물}이 바로 콜백 함수입니다.

 

2. useEffect의 역할:

  • 컴포넌트가 렌더링되거나 의존성 배열([])에 명시된 값이 변경될 때, 이 콜백 함수를 실행합니다.
  • 의존성 배열이 비어 있다면, 콜백 함수는 컴포넌트가 "처음 마운트될 때" 한 번만 실행됩니다.

3. 클린업 함수(optional):

  • 콜백 함수 내부에서 리턴된 함수가 있다면, 그것은 컴포넌트가 언마운트되거나 의존성 배열에 명시된 값이 변경되기 전에 실행됩니다.

코드에서의 실제 동작

useEffect(() => {
    (async () => {
        let data = null;
        setLoading(true);

        try {
            data = await axiosHelper.get("/titanic");
            console.log(data);
        } catch (e) {
            alert(e.message);
            return;
        }

        setTitanicData(data.item);
        setLoading(false);
    })(); // 즉시 실행 함수
}, []); // 의존성 배열
  • ()=> {안의 내용물}은 useEffect의 콜백 함수로 전달됩니다.
  • useEffect는 이 콜백 함수가 실행되도록 관리합니다.
  • 여기서 콜백 함수 안에 비동기 작업을 처리하기 위해 즉시 실행 함수 (async () => {...})()를 넣은 것입니다.

결론

useEffect는 단순히 전달받은 콜백 함수(()=> {안의 내용물})를 관리하고 적절한 타이밍에 실행시킬 뿐입니다.

이 구조는 React의 선언적 프로그래밍 방식에 맞춘 설계로, 명시적으로 라이프사이클을 관리하는 대신 React가 실행 시점을 자동으로 관리하게 만드는 중요한 개념입니다.

 
 
 
Q3.

나의 말:

그리고 해당 코드에서는, {안의 내용물} 이 부분이, async 가 붙은 함수를 직접적으로 적용할 수 없는 리엑트 훅을 대신해서, async()=>{} 형태의 콜백함수를, ()로 감싸고, 그 뒤에 ()를 이어 붙여서, 즉시실행함수 꼴로 만든 것이지?

 
 
 
A3.

ChatGPT의 말:

 

네, 맞습니다! 👍

해당 코드에서는 React의 useEffect에 전달되는 콜백 함수에 async를 직접 사용할 수 없기 때문에, 이를 대신하기 위해 즉시 실행 함수(IIFE) 형태를 활용한 것입니다.

왜 useEffect에 async를 직접 사용할 수 없나요?

React의 useEffect에 전달되는 콜백 함수는 동기적으로 실행되어야 하기 때문입니다. async 함수는 항상 Promise를 반환하므로, React는 반환된 Promise를 어떻게 처리해야 할지 몰라 에러가 발생할 수 있습니다.

useEffect(async () => {  // 이렇게 하면 React가 혼란스러워합니다.
    // 코드
}, []);

위와 같은 방식은 허용되지 않으므로, 이를 우회하기 위해 **즉시 실행 함수(IIFE)**를 사용합니다.


코드에서 사용된 즉시 실행 함수의 구조

즉시 실행 함수는 아래와 같은 형식입니다:

(async () => {
    // 비동기 작업 처리
})();

이것을 useEffect 안에 넣으면 다음과 같이 동작합니다:

  1. useEffect의 콜백 함수: useEffect는 동기적으로 실행되는 콜백 함수 ()=>{...}를 받습니다.
  2. 콜백 함수 안에 즉시 실행 함수 작성: 콜백 함수 내부에서 비동기 작업을 처리하기 위해, 즉시 실행 함수(IIFE)로 비동기 작업을 처리합니다.
  3. IIFE의 실행: IIFE는 선언과 동시에 실행되므로, 내부의 비동기 작업이 곧바로 실행됩니다.

해당 코드 분석

useEffect(() => {
    // IIFE를 선언하고 실행
    (async () => { 
        let data = null;
        setLoading(true);

        try {
            // 비동기 작업 처리
            data = await axiosHelper.get("/titanic");
            console.log(data);
        } catch (e) {
            alert(e.message);
            return; // 에러 발생 시 중단
        }

        // 데이터를 상태로 설정
        setTitanicData(data.item);
        setLoading(false);
    })(); // 즉시 실행 함수의 끝
}, []);
  • useEffect의 콜백 함수는 동기적이며, 내부에서 비동기 처리가 필요하기 때문에 즉시 실행 함수 (async()=>{})()를 사용했습니다.
  • 이 IIFE는 선언과 동시에 실행되므로, 비동기 로직이 바로 실행됩니다.

결론

네, 말씀하신 대로, React Hook에 async를 직접 사용할 수 없는 한계를 극복하기 위해 콜백 함수 안에 **즉시 실행 함수(IIFE)**를 만들어 async를 적용한 것입니다. 이는 비동기 작업을 React의 라이프사이클 내에서 안전하게 처리하는 일반적인 패턴입니다.

 
 
 
Q4.

나의 말:

왜 async 붙은 함수를 즉시 실행 함수로 만들어준것이지? 그냥 익명함수 상태로 놔두면 안되는거야?

 
 
 
A4.

ChatGPT의 말:

 

좋은 질문이에요! 😊
익명 함수로 두는 것과 즉시 실행 함수로 만드는 것의 차이를 이해하면, 왜 이렇게 구현했는지 명확해집니다.

왜 즉시 실행 함수(IIFE)로 만들었을까?

1. 익명 함수 상태로 두었을 때

useEffect(() => {
    async () => {
        let data = await axiosHelper.get("/titanic");
        console.log(data);
    };
}, []);

위 코드처럼 단순히 async () => {...}로 정의만 하고 실행하지 않으면, 해당 함수는 선언만 되고 실행되지 않습니다. React가 useEffect에서 이 익명 함수를 실행해주지 않기 때문입니다. 결국, axiosHelper.get() 등의 비동기 작업이 전혀 실행되지 않게 됩니다.


2. 즉시 실행 함수(IIFE)를 사용했을 때

useEffect(() => {
    (async () => {
        let data = await axiosHelper.get("/titanic");
        console.log(data);
    })(); // 함수를 선언하고 즉시 실행
}, []);

여기서 IIFE의 역할은:

  • 함수를 선언하자마자 실행시켜주는 것입니다.
  • 즉, 비동기 작업이 즉각적으로 실행됩니다.
  • 이렇게 하면 useEffect의 콜백 함수가 호출될 때, IIFE 내부의 비동기 코드가 실행됩니다.

익명 함수 상태로 두는 것과 즉시 실행 함수의 차이


익명 함수로 선언만 한 경우 즉시 실행 함수로 만든 경우
함수는 선언만 되고 실행되지 않음. 함수가 선언과 동시에 실행됨.
비동기 작업(axiosHelper.get())이 실행되지 않음. 비동기 작업이 즉시 실행됨.
잘못된 사용 사례. 비동기 로직이 무의미하게 됨. 의도한 대로 비동기 작업이 작동함.

왜 다른 방법이 아니라 IIFE를 썼을까?

  1. useEffect의 콜백 함수는 동기적으로 실행되어야 하므로, 비동기 작업을 직접 넣을 수 없기 때문입니다.
  2. 비동기 작업을 실행하려면 별도로 함수 호출을 트리거해야 하는데, IIFE는 선언과 동시에 실행되므로 별도의 호출 코드가 필요 없습니다.
  3. IIFE는 코드를 깔끔하게 작성하면서도 비동기 처리를 자연스럽게 추가할 수 있는 일반적인 패턴입니다.

대안: 함수 선언 후 호출

물론 IIFE가 아닌 방식으로도 구현할 수 있습니다. 예를 들어, 익명 함수 대신 별도로 선언한 함수를 호출하는 방법도 가능합니다:

useEffect(() => {
    const fetchData = async () => {
        let data = await axiosHelper.get("/titanic");
        console.log(data);
    };

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

이 방법도 잘 작동하며, 가독성을 조금 더 높일 수도 있습니다. 둘 중 어느 방식을 사용해도 괜찮습니다.


결론

async 함수는 선언만 해서는 실행되지 않으므로, 즉시 실행 함수(IIFE)로 감싸서 선언과 동시에 실행한 것입니다. 이는 useEffect 안에서 비동기 작업을 처리할 때 흔히 사용하는 패턴입니다. 대안으로 함수 선언 후 호출 방식도 가능합니다.

 
 
 
Q5.

나의 말:

즉, useEffect 훅은, 파라미터로 받은 콜백함수를 실행은 시키나, 콜백함수 안에 있는 함수 자체는 즉시 실행시키지 않는다는 말인가? 그리고 해당 내용은 다른 모든 훅들에게도 동일하게 적용되는가?
 
 
 
A5.

ChatGPT의 말:

 

네, 정확합니다! 🙂 useEffect를 비롯한 React의 훅(Hook)들은 파라미터로 전달된 콜백 함수를 실행하는 역할만 합니다. 그러나 그 콜백 함수 내에서 선언된 함수(예: async () => {})는 React가 자동으로 실행하지 않습니다. 이를 개발자가 명시적으로 호출하거나, 즉시 실행 함수(IIFE) 형태로 만들어야 실행됩니다.


1. useEffect의 동작 원리

React의 useEffect는 다음과 같은 방식으로 작동합니다:

  • 콜백 함수 자체는 실행됨: useEffect는 첫 번째 인자로 받은 콜백 함수를 실행합니다.
  • 콜백 함수 안에서 선언된 함수는 실행되지 않음: 콜백 함수 내부에서 선언된 함수(익명 함수, async 함수 등)는 실행되지 않고 단순히 선언만 됩니다. 이를 실행하려면 명시적으로 호출하거나 IIFE로 만들어야 합니다.

예시를 보겠습니다:

실행되지 않는 경우 (단순 선언)

useEffect(() => {
    async () => {
        console.log("이 함수는 실행되지 않습니다.");
    };
}, []);

여기서 async () => {}는 단순히 함수 선언일 뿐 실행되지 않습니다.


실행되는 경우 (명시적으로 호출)

useEffect(() => {
    const fetchData = async () => {
        console.log("이 함수는 호출되었으므로 실행됩니다.");
    };

    fetchData(); // 명시적으로 호출
}, []);

이 방식은 함수 선언 후 명시적으로 호출하므로 실행됩니다.


실행되는 경우 (IIFE로 즉시 실행)

useEffect(() => {
    (async () => {
        console.log("이 함수는 즉시 실행됩니다.");
    })(); // IIFE 형태로 실행
}, []);

여기서 함수 선언과 호출이 동시에 이루어지므로 실행됩니다.


2. 다른 훅에도 동일한 원칙이 적용되나요?

네, React의 다른 훅들(useState, useContext, useReducer, useCallback, useMemo 등)에도 같은 원칙이 적용됩니다. React 훅은 단지 React의 상태 관리 및 렌더링 로직을 돕기 위해 특정 기능을 제공할 뿐이며, 콜백 함수 내부에서 선언된 함수들을 자동으로 실행하지 않습니다.

useState 예시

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

useEffect(() => {
    const increment = () => {
        setCount(prev => prev + 1);
    };

    increment(); // 명시적으로 호출해야 실행됨
}, []);

increment를 선언만 하고 호출하지 않으면, 아무 일도 일어나지 않습니다.

useCallback 예시

const increment = useCallback(() => {
    console.log("increment 함수가 호출되었습니다.");
}, []);

increment(); // 명시적으로 호출해야 실행됨

여기서도 useCallback은 메모이제이션만 제공하며, 함수 실행은 개발자가 명시적으로 해야 합니다.


3. 결론

  • React의 모든 훅(useEffect, useState, useCallback, 등)은 콜백 함수나 반환 값을 자동으로 실행하지 않습니다. 콜백 내부에 선언된 함수는 실행되지 않고 단순히 선언으로만 남습니다.
  • 따라서, 콜백 내부의 함수는 명시적으로 호출하거나, **즉시 실행 함수(IIFE)**로 선언과 동시에 실행해야 동작합니다.
 
 
 
Q6.

나의 말:

 

메모이제이션이란 말은 뭐지? 핵심 개념만 설명해줘. 아직은 정확히 짚고 갈 수 있는 수준에 내가 도달해있지 않아. 굳이 예시도 짚어주지 않아도 돼.

 
 
 
A6.

ChatGPT의 말:

 

메모이제이션(Memoization)은 결과를 재활용하기 위해 함수의 실행 결과를 캐싱(저장)하는 최적화 기법입니다.

  • 핵심 개념: 동일한 입력 값에 대해 함수가 반복적으로 호출될 때, 이전 호출의 결과를 기억해서 불필요한 계산을 피하고 성능을 향상시킵니다.
  • 목적: 성능 최적화, 특히 시간이 많이 걸리는 연산이나 반복되는 작업에서 유용합니다.

간단한 비유

  • "문제를 한 번 풀었으면, 같은 문제를 또 풀지 말고 답을 기억해둔다"는 개념입니다.

React에서는 useMemo와 useCallback 같은 훅에서 이 개념이 사용됩니다. 렌더링 시 불필요한 작업을 줄이고 성능을 최적화하기 위한 도구로 활용됩니다.

언제나 추가 설명이 필요하면 알려주세요! 😊

 
 
 
Q7.

나의 말:

다시 내가 공부하고 있는 내용으로 돌아와서, 내가 맨 처음에 보낸 useEffect 훅의 형태는, async가 붙은 콜백함수는 파라미터로 직접 받을 수 없기에, useEffect에 파라미터로 들어간 콜백함수가 작동시킬 코드로, async가 붙은 익명 함수를 즉시 실행 함수 꼴로 넣어준 것이지?

 
 
 
A7.

ChatGPT의 말:

 

네, 맞습니다! 😊

정리하면:

  1. useEffect에 async가 붙은 콜백 함수는 직접 전달할 수 없습니다.
    • React의 useEffect는 동기적 콜백 함수만 허용합니다. async 함수를 전달하면 항상 Promise를 반환하므로 React에서 예상하지 못한 동작을 유발할 수 있습니다.
  2. 그래서 useEffect에 전달한 콜백 함수가 실행할 코드로:
    • 비동기 작업이 필요한 경우, async를 사용하는 익명 함수를 정의합니다.
    • 정의한 익명 함수를 즉시 실행 함수(IIFE) 형태로 만들어서, useEffect가 호출될 때 자동으로 실행되도록 합니다.

핵심 구조:

useEffect(() => {
    (async () => {
        // 비동기 작업 처리
    })(); // 즉시 실행 함수로 만들어 실행
}, []);
  • 이 패턴을 통해 useEffect 내부에서 비동기 작업을 수행하면서 React의 규칙을 지킬 수 있습니다.

요약

  • useEffect는 동기적 콜백 함수만 받습니다.
  • 비동기 작업이 필요하면, 콜백 함수 내부에 **async를 붙인 즉시 실행 함수(IIFE)**를 사용해 비동기 로직을 실행합니다.

질문하신 코드의 형태는 정확히 이 구조를 따르고 있습니다. 👌


 

Q8.

마지막으로 확인할게. async가 붙은 함수를, hook에 직접 걸 수는 없는거지? 해당 내용에 대한 예시는 필요없어. 그냥 내 질문에 대한 깔끔한 답만 해주었으면 좋겠어.
 
 
 
A8.

ChatGPT의 말:

 

네, async가 붙은 함수를 React의 Hook에 직접 전달할 수 없습니다.

React의 Hook (useEffect, useCallback, 등)은 동기적인 콜백 함수만 허용합니다.