useEffect

수툴리 양·2021년 9월 26일
0

React JS

목록 보기
2/2

keyword

  • implicit promise
  • clean up function

useEffect 에서 비동기 처리 방식들의 차이. 안에서 함수를 선언하고 호출하는 것과.. 밖에서 정의하고 호출하는 것과 .. 등등

⌑ 비동기 제어

: useEffect 내부에서 함수로 감싸서(선언해서) call 하는 것과 예를 들어 그냥 바로 axios 요청하는 것과 차이

  • 관련 에러
    index.js:1 Warning: **An effect function must not return anything besides a function, which is used for clean-up.**

    It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, **write the async function inside your effect and call it immediately**:

    useEffect(() => {
      async function fetchData() {
        // You can await here
        const response = await MyAPI.getData(someId);
        // ...
      }
      fetchData();
    }, [someId]); // Or [] if effect doesn't need props or state

    Learn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching
        in App (at src/index.js:9)
        in Router (created by BrowserRouter)
        in BrowserRouter (at src/index.js:8)

위와 같이 Warning이 뜸. async + await 로 비동기 제어해주고 fetchData() 이런 식으로 함수를 호출(실행)해주는 형태를 권고함

async 함수는 promise 객체를 리턴함(implicit), 그러나 effect hook은 아무것도 리턴하지 않거나 clean up 함수 자체를 리턴해야 하므로, 바로 async 함수를 작성하지 않고 함수를 내부에서 정의해서 call하는 것임


🔗 참고 링크 🌟 *good!

정리

  • 외부 API 요청하여 데이터를 fetch해오고 이를 ⇒ local state로 사용(update)하기 위해 useEffect hook을 이용.
  • dependency [] 에 의해 무한히 useEffect 가 호출될 수 있음(component가 update될 때마다)
  • []비어 있을 때만 최초 렌더 시 (when component mounts) fetch해 온다.

에러 관련 핵심!

  • async/await를 쓰는 이유는 외부 api 콜을 요청하기 때문임. 비동기로 처리하기 위해.(using async/await to fetch data from a third-party API)

결론

  • async함수는 비동기함수 객체 즉 promise 객체를 리턴함.(implicit promise)
  • effect hook은 아무것도 반환하지 않거나 clean up 함수를 리턴해야 함
  • 그래서 이런 오류가 뜨는 것이고, async함수를 useEffect에서 바로 사용하지 말라고 warning이 뜨는 것.

원문
The effect hook called useEffect is used to fetch the data with axios from the API and to set the data in the local state of the component with the state hook's update function. The promise resolving happens with async/await.
However, when you run your application, you should stumble into a nasty loop. The effect hook runs when the component mounts but also when the component updates. Because we are setting the state after every data fetch, the component updates and the effect runs again. It fetches the data again and again. That's a bug and needs to be avoided. We only want to fetch data when the component mounts. That's why you can provide an empty array as second argument to the effect hook to avoid activating it on component updates but only for the mounting of the component.

function App() {
  const [data, setData] = useState({ hits: [] });
  useEffect(**async** () => {
    const result = **await** axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
    setData(result.data);
  }, **[]**);

The second argument can be used to define all the variables (allocated in this array) on which the hook depends. If one of the variables changes, the hook runs again. If the array with the variables is empty, the hook doesn't run when updating the component at all, because it doesn't have to watch any variables.
There is one last catch. In the code, we are using async/await to fetch data from a third-party API. According to the documentation every function annotated with async returns an implicit promise"The async function declaration defines an asynchronous function, which returns an AsyncFunction object. An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result. ". However, an effect hook should return nothing or a clean up function. That's why you may see the following warning in your developer console log: 07:41:22.910 index.js:1452 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => ...) are not supported, but you can call an async function inside an effect.. That's why using async directly in the useEffect function isn't allowed. Let's implement a workaround for it, by using the async function inside the effect.

function App() {
  const [data, setData] = useState({ hits: [] });
  useEffect(() => {
   ** const fetchData = async () => {**
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );
      setData(result.data);
    };
   ** fetchData();**
  }, []);

useEffect 내부에서 함수를 정의해야 하나? 표현식(const=()=>{})과 선언식(function(){}) 의 차이는?

useEffect 내부에서 함수를 정의하는 이유*효과
1. 외부에 정의 시 useCallback으로 감싸야 함
2. 렌더 할 때마다 함수가 선언될 필요가 없다. useEffect 에서만 사용하는 함수라면 특히.
3. useEffect 내부에서 변수나 함수를 선언함으로써 비동기 제어+ cleanup이 더 쉬움.

🔗 참고 링크(스택오버플로우)

Why do we define function inside useEffect?
: reasons

  1. By definining the function outside of the use effect, you either need to disable exhaustive-deps and risk accidentally having a stale function or you need to useCallback to make the function not-update every render
  2. If the function is only used in the useEffect, you don't need to recreate the function on every render as that's just wasted cycles
  3. It's easier to work with cleanup on the asynchronous functions by defining it within useEffect as you can define variables that are able to be modified within the effect.

On that last one, for instance, you can do some actions to prevent state being called when the effect cleans up.

주의 arrow function (const functionname = () ⇒ {} *표현식 + arrow ) 는 hositing 되지 않음!

handleLogout 정의 시 선언식 으로. (function name () {})

*useEffect 내부에서 선언식으로 표현식으로 하는 것의 차이? 스코프 차이인가?
선언식은 호이스팅되면서 전역변수화?
es6 이후로 sugar syntax 되어 상관없다고 함, same scope를 가짐!
리액트 hook은 기본적으로 순수함수(형 컴포넌트)에서 쓸 수 있음을 기억


✪ clean up

🔗 참고 공식문서

  • cleanup이 필요한 side effect 의 경우, 메모리 누수를 방지하기 위해 clean up 함수 필요

    important to clean up so that we don’t introduce a memory leak

  • clean up 이라 함은 useEffect 내 return 이 있는 것

    외부로부터 데이터를 구독하는 경우? (set up a subscription to some external data source)

클래스 컴포넌트의 경우
componentWillUnmount로 clean up을 처리함(componentDidMount 에서 외부데이터리소스 구독을 처리하고)
*Lifecycle methods force us to split this logic even though conceptually code in both of them is related to the same effect.

by using Hooks
useEffect Hook 자체가 두 상황?을 다룰 수 있도록 고안됨
: (구독 해제-componentWillUnmount 구독 처리-componentDidMount 의 경우)
‣subscription 추가, 제거 코드는 결합도가 높기 때문에.
effect가 함수를 리턴하면 리액트는 cleanup 필요 시 그 함수를 실행함.
You might be thinking that we’d need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up


when exactly does React clean up an effect?

컴포넌트가 unmount 될 때 또 한번의 effect 실행 전 이전 렌더에서의 effect를 지우는 것이다.

React performs the cleanup when the component unmounts. .. effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.

사실 componentDidUpdate 가 있어야 확실한 보완?이 됨 🔗 [참고][공식문서](https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update)

⇒ 클래스 컴포넌트에서 생길 수 있는 bug를 방지하고 consistency를 보장할 수 있는 방법임

*여기서 말하는 버그란: 컴포넌트가 뷰에 렌더된 상태에서 props값이 변동이 생겼을 때, 이전 데이터를 계속 보여주는 상태 일 수 있음. memory leak나 crash.

ensures consistency by default and prevents bugs that are common in class components due to missing update logic.

effect 발생 시나 cleanup 시에 이러한 버그 있을 수 있으므로, DidUpdate 에서 prevPropsprevState 를 사용해서 effect를 skip 하도록 처리하기도 ..

  • 참고 링크(스택오버플로우)

    You're returning the result of calling getResponse() from the useEffect function. If you return anything from useEffect, it has to be a function. Changing your code to this should fix it because you're no longer returning anything from the useEffect function.

    useEffect(() => {
      getResponse();
    });

The useEffect Cleanup Function


If you return anything from the useEffect hook function, it must be a cleanup function. This function will run when the component unmounts. This can be thought of as roughly equivalent to the componentWillUnmount lifecycle method in class components.

profile
developer; not kim but Young

0개의 댓글