useEffect의 비동기 함수에 대한 React Hook 경고 : useEffect 함수는 정리 함수를 반환하거나 아무것도 반환하지 않아야합니다.


117

아래와 같은 useEffect 예제를 시도했습니다.

useEffect(async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}, []);

내 콘솔에이 경고가 표시됩니다. 그러나 정리는 내가 생각하는 비동기 호출의 경우 선택 사항입니다. 이 경고를받는 이유를 잘 모르겠습니다. 예제를 위해 샌드 박스 연결. https://codesandbox.io/s/24rj871r0p 여기에 이미지 설명 입력

답변:


164

나는 Dan Abramov (React 핵심 관리자 중 한 명) 대답을 여기 에서 볼 것을 제안합니다 .

필요 이상으로 복잡하게 만들고 있다고 생각합니다.

function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}

장기적으로는이 패턴이 경쟁 조건을 조장하기 때문에 권장하지 않을 것입니다. 예를 들어, 호출 시작과 종료 사이에 어떤 일이 발생할 수 있으며 새로운 소품을 얻을 수 있습니다. 대신 데이터 가져 오기를 위해 Suspense를 권장합니다.

const response = MyAPIResource.read();

효과가 없습니다. 그러나 그 동안 비동기 항목을 별도의 함수로 이동하여 호출 할 수 있습니다.

여기에서 실험적 서스펜스 에 대해 자세히 알아볼 수 있습니다 .


eslint와 함께 외부 기능을 사용하려는 경우.

 function OutsideUsageExample() {
  const [data, dataSet] = useState<any>(null)

  const fetchMyAPI = useCallback(async () => {
    let response = await fetch('api/data')
    response = await response.json()
    dataSet(response)
  }, [])

  useEffect(() => {
    fetchMyAPI()
  }, [fetchMyAPI])

  return (
    <div>
      <div>data: {JSON.stringify(data)}</div>
      <div>
        <button onClick={fetchMyAPI}>manual fetch</button>
      </div>
    </div>
  )
}

useCallback 사용 useCallback . 샌드 박스 .

import React, { useState, useEffect, useCallback } from "react";

export default function App() {
  const [counter, setCounter] = useState(1);

  // if counter is changed, than fn will be updated with new counter value
  const fn = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);

  // if counter is changed, than fn will not be updated and counter will be always 1 inside fn
  /*const fnBad = useCallback(() => {
      setCounter(counter + 1);
    }, []);*/

  // if fn or counter is changed, than useEffect will rerun
  useEffect(() => {
    if (!(counter % 2)) return; // this will stop the loop if counter is not even

    fn();
  }, [fn, counter]);

  // this will be infinite loop because fn is always changing with new counter value
  /*useEffect(() => {
    fn();
  }, [fn]);*/

  return (
    <div>
      <div>Counter is {counter}</div>
      <button onClick={fn}>add +1 count</button>
    </div>
  );
}

구성 요소가 다음과 같이 마운트 해제되었는지 확인하여 경쟁 조건 문제를 해결할 수 있습니다. useEffect(() => { let unmounted = false promise.then(res => { if (!unmounted) { setState(...) } }) return () => { unmounted = true } }, [])
Richard

1
use-async-effect 라는 패키지를 사용할 수도 있습니다 . 이 패키지를 사용하면 async await 구문을 사용할 수 있습니다.
KittyCat

자체 호출 함수를 사용하면 비동기가 useEffect 함수 정의로 누출되지 않도록하거나 useEffect를 둘러싼 래퍼로 비동기 호출을 트리거하는 함수의 사용자 지정 구현을 사용하는 것이 가장 좋습니다. 제안 된 use-async-effect와 같은 새 패키지를 포함 할 수 있지만 이것이 해결하기 쉬운 문제라고 생각합니다.
Thulani Chivandikwa

1
괜찮아요. 그리고 제가 대부분의 시간을 해요. 하지만 eslint나에게 fetchMyAPI()의존성 으로 만들 것을 요청합니다useEffect
Prakash Reddy Potlapadu

51

다음과 같은 비동기 함수를 사용할 때

async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}

약속을 반환하고 useEffect 콜백 함수가 약속을 반환하는 기대하지 않는, 오히려 아무것도 반환되지 않습니다 또는 함수가 반환되는 것을 기대하고있다.

경고에 대한 해결 방법으로 자체 호출 비동기 함수를 사용할 수 있습니다.

useEffect(() => {
    (async function() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    })();
}, []);

또는 더 깔끔하게 만들기 위해 함수를 정의한 다음 호출 할 수 있습니다.

useEffect(() => {
    async function fetchData() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    };
    fetchData();
}, []);

두 번째 솔루션을 사용하면 읽기가 더 쉬워지고 새 요청이 실행되면 이전 요청을 취소하거나 최신 요청 응답을 상태로 저장하는 코드를 작성하는 데 도움이됩니다.

작업 코드 샌드 박스


이것을 쉽게하기위한 패키지가 만들어졌습니다. 여기에서 찾을 수 있습니다 .
KittyCat 2018

1
하지만 eslint 그와 용납하지 않습니다
Muhaimin CS

1
정리 / didmount 콜백을 수행 할 수있는 방법은 없습니다
데이비드 Rearte

1
@ShubhamKhatri를 사용할 때 useEffect이벤트 구독 취소와 같은 정리를 수행하는 함수를 반환 할 수 있습니다. 이 기능 비동기 사용하는 경우 때문에 당신은 아무것도를 반환 할 수없는 useEffect결과를 기다리지 않습니다
데이비드 Rearte

2
비동기식으로 정리 기능을 넣을 수 있다는 말입니까? 시도했지만 정리 기능이 호출되지 않았습니다. 약간의 예를들 수 있습니까?
David Rearte 2019

32

React가 더 나은 방법을 제공 할 때까지 도우미를 만들 수 있습니다 useEffectAsync.js.

import { useEffect } from 'react';


export default function useEffectAsync(effect, inputs) {
    useEffect(() => {
        effect();
    }, inputs);
}

이제 비동기 함수를 전달할 수 있습니다.

useEffectAsync(async () => {
    const items = await fetchSomeItems();
    console.log(items);
}, []);

8
React가 useEffect에서 비동기 함수를 자동으로 허용하지 않는 이유는 대부분의 경우 일부 정리가 필요하기 때문입니다. 기능 useAsyncEffect당신이 쓴대로 그것은 그들은 그것이 적절한 시간에 실행 될 자신의 비동기 효과에서 정리 기능을 생각에 쉽게 오해 사람을 반환 할 수 있다면. 이로 인해 메모리 누수가 발생하거나 버그가 더 악화 될 수 있으므로 React의 수명주기와 상호 작용하는 비동기 함수의 "원활한" "원활한"을 만들고 그 결과로 코드의 동작을보다 신중하고 정확하게 만들기 위해 코드를 리팩토링하도록 권장했습니다.
Sophie Alpert

8

이 질문을 읽었고 useEffect를 구현하는 가장 좋은 방법이 답변에 언급되어 있지 않다고 생각합니다. 네트워크 통화가 있고 응답을 받으면 무언가를하고 싶다고 가정 해 보겠습니다. 단순화를 위해 네트워크 응답을 상태 변수에 저장해 보겠습니다. 조치 / 감소기를 사용하여 네트워크 응답으로 상점을 업데이트 할 수 있습니다.

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

/* This would be called on initial page load */
useEffect(()=>{
    fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(data => {
        setData(data);
    })
    .catch(err => {
        /* perform error handling if desired */
    });
}, [])

/* This would be called when store/state data is updated */
useEffect(()=>{
    if (data) {
        setPosts(data.children.map(it => {
            /* do what you want */
        }));
    }
}, [data]);

참조 => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects


1

시험

const MyFunctionnalComponent: React.FC = props => {
  useEffect(() => {
    // Using an IIFE
    (async function anyNameFunction() {
      await loadContent();
    })();
  }, []);
  return <div></div>;
};


1

다른 독자의 경우 비동기 함수를 래핑하는 대괄호가 없다는 사실에서 오류가 발생할 수 있습니다.

비동기 함수 initData 고려

  async function initData() {
  }

이 코드는 오류로 이어집니다.

  useEffect(() => initData(), []);

그러나 이것은 다음을 수행하지 않습니다.

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

(initData () 주위의 괄호에 유의하십시오.


1
훌륭 해요! 나는 saga를 사용하고 있으며 유일한 객체를 반환하는 액션 생성자를 호출 할 때 그 오류가 나타났습니다. 콜백 함수가이 동작을 핥지 않는 useEffect처럼 보입니다. 귀하의 답변에 감사드립니다.
Gorr1995

2
사람들이 왜 이것이 사실인지 궁금해하는 경우 ... 중괄호가 없으면 initData ()의 반환 값은 화살표 함수에 의해 암시 적으로 반환됩니다. 중괄호를 사용하면 암시 적으로 아무 것도 반환되지 않으므로 오류가 발생하지 않습니다.
Marnix.hoh

1

여기에 void 연산자를 사용할 수 있습니다.
대신에:

React.useEffect(() => {
    async function fetchData() {
    }
    fetchData();
}, []);

또는

React.useEffect(() => {
    (async function fetchData() {
    })()
}, []);

다음과 같이 작성할 수 있습니다.

React.useEffect(() => {
    void async function fetchData() {
    }();
}, []);

조금 더 깨끗하고 예쁘다.


비동기 효과로 인해 메모리 누수가 발생할 수 있으므로 구성 요소 마운트 해제시 정리를 수행하는 것이 중요합니다. 가져 오기의 경우 다음과 같이 보일 수 있습니다.

function App() {
    const [ data, setData ] = React.useState([]);

    React.useEffect(() => {
        const abortController = new AbortController();
        void async function fetchData() {
            try {
                const url = 'https://jsonplaceholder.typicode.com/todos/1';
                const response = await fetch(url, { signal: abortController.signal });
                setData(await response.json());
            } catch (error) {
                console.log('error', error);
            }
        }();
        return () => {
            abortController.abort(); // cancel pending fetch request on component unmount
        };
    }, []);

    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.