React Hooks로 데이터를 가져올 때 경쟁 조건 방지

ReactuseEffect 후크는 기능 구성 요소에서 부작용을 수행하는 데 적합합니다. 이에 대한 한 가지 일반적인 예는 데이터를 가져오는 것입니다. 그러나 효과를 정리하는 데 주의를 기울이지 않으면 경쟁 조건이 발생할 수 있습니다! 이 게시물에서는 이러한 경쟁 조건 문제가 발생하지 않도록 효과를 적절하게 정리할 것입니다.

설정



예제 앱에서는 이름을 클릭하면 사람들의 프로필 데이터를 가짜로 로드할 것입니다. 경합 상태를 시각화하는 데 도움이 되도록 0초에서 5초 사이의 임의 지연을 구현하는 fakeFetch 함수를 만듭니다.

const fakeFetch = person => {
  return new Promise(res => {
    setTimeout(() => res(`${person}'s data`), Math.random() * 5000);
  });
};


초기 구현



초기 구현에서는 단추를 사용하여 현재 프로필을 설정합니다. 다음 상태를 유지하면서 이를 구현하기 위해 useState 후크에 도달합니다.
  • person , 사용자가 선택한 인물
  • data , 선택한 사람
  • 을 기준으로 가짜 가져오기에서 로드된 데이터
  • loading , 데이터가 현재 로드되고 있는지 여부
  • useEffect가 변경될 때마다 가짜 가져오기를 수행하는 person 후크를 추가로 사용합니다.

    import React, { Fragment, useState, useEffect } from 'react';
    
    const fakeFetch = person => {
      return new Promise(res => {
        setTimeout(() => res(`${person}'s data`), Math.random() * 5000);
      });
    };
    
    const App = () => {
      const [data, setData] = useState('');
      const [loading, setLoading] = useState(false);
      const [person, setPerson] = useState(null);
    
      useEffect(() => {
        setLoading(true);
        fakeFetch(person).then(data => {
          setData(data);
          setLoading(false);
        });
      }, [person]);
    
      return (
        <Fragment>
          <button onClick={() => setPerson('Nick')}>Nick's Profile</button>
          <button onClick={() => setPerson('Deb')}>Deb's Profile</button>
          <button onClick={() => setPerson('Joe')}>Joe's Profile</button>
          {person && (
            <Fragment>
              <h1>{person}</h1>
              <p>{loading ? 'Loading...' : data}</p>
            </Fragment>
          )}
        </Fragment>
      );
    };
    export default App;
    


    앱을 실행하고 버튼 중 하나를 클릭하면 가짜 가져오기가 예상대로 데이터를 로드합니다.

    경쟁 조건에 도달



    문제는 사람들 사이를 빠르게 전환하기 시작할 때 발생합니다. 가짜 가져오기에 임의의 지연이 있다는 사실을 감안할 때 가져오기 결과가 잘못된 순서로 반환될 수 있음을 곧 알게 됩니다. 또한 선택한 프로필과 로드된 데이터가 동기화되지 않을 수 있습니다. 나쁜 표정이야!



    여기서 일어나는 일은 상대적으로 직관적입니다. setData(data) 후크 내의 useEffectfakeFetch 약속이 해결된 후에만 호출됩니다. 실제로 어떤 버튼이 마지막으로 호출되었는지에 관계없이 어떤 약속이 마지막으로 해결되든 마지막으로 setData를 호출합니다.

    이전 가져오기 취소



    가장 최근이 아닌 클릭에 대한 호출setData을 "취소"하여 이 경합 상태를 수정할 수 있습니다. useEffect 후크 내에서 범위가 지정된 부울 변수를 만들고 이 부울 "취소된"변수를 useEffect로 설정하는 정리 함수를 true 후크에서 반환하여 이를 수행합니다. 약속이 확인되면 "취소된"변수가 false인 경우에만 setData가 호출됩니다.

    해당 설명이 다소 혼란스럽다면 useEffect 후크의 다음 코드 샘플이 도움이 될 것입니다.

    useEffect(() => {
      let canceled = false;
    
      setLoading(true);
      fakeFetch(person).then(data => {
        if (!canceled) {
          setData(data);
          setLoading(false);
        }
      });
    
      return () => (canceled = true);
    }, [person]);
    


    이전 버튼 클릭의 fakeFetch 약속이 나중에 확인되더라도 해당 canceled 변수는 true로 설정되고 setData(data)는 실행되지 않습니다!

    새로운 앱이 어떻게 작동하는지 살펴보겠습니다.



    완벽함 - 다른 버튼을 몇 번 클릭하더라도 항상 마지막 버튼 클릭과 관련된 데이터만 표시됩니다.

    전체 코드



    이 블로그 게시물의 전체 코드는 아래에서 찾을 수 있습니다.

    import React, { Fragment, useState, useEffect } from 'react';
    
    const fakeFetch = person => {
      return new Promise(res => {
        setTimeout(() => res(`${person}'s data`), Math.random() * 5000);
      });
    };
    
    const App = () => {
      const [data, setData] = useState('');
      const [loading, setLoading] = useState(false);
      const [person, setPerson] = useState(null);
    
      useEffect(() => {
        let canceled = false;
    
        setLoading(true);
        fakeFetch(person).then(data => {
          if (!canceled) {
            setData(data);
            setLoading(false);
          }
        });
    
        return () => (canceled = true);
      }, [person]);
    
      return (
        <Fragment>
          <button onClick={() => setPerson('Nick')}>Nick's Profile</button>
          <button onClick={() => setPerson('Deb')}>Deb's Profile</button>
          <button onClick={() => setPerson('Joe')}>Joe's Profile</button>
          {person && (
            <Fragment>
              <h1>{person}</h1>
              <p>{loading ? 'Loading...' : data}</p>
            </Fragment>
          )}
        </Fragment>
      );
    };
    export default App;
    

    좋은 웹페이지 즐겨찾기