[reactjs] componentWillUnmount에서 가져 오기를 취소하는 방법

제목이 다 나와 있다고 생각합니다. 여전히 페치중인 구성 요소를 마운트 해제 할 때마다 노란색 경고가 표시됩니다.

콘솔

경고 : 마운트 해제 된 구성 요소 에서는 호출 setState(또는 forceUpdate) 할 수 없습니다 . 이것은 작동하지 않지만 … 수정하려면 componentWillUnmount메서드의 모든 구독 및 비동기 작업을 취소하십시오 .

  constructor(props){
    super(props);
    this.state = {
      isLoading: true,
      dataSource: [{
        name: 'loading...',
        id: 'loading',
      }]
    }
  }

  componentDidMount(){
    return fetch('LINK HERE')
      .then((response) => response.json())
      .then((responseJson) => {
        this.setState({
          isLoading: false,
          dataSource: responseJson,
        }, function(){
        });
      })
      .catch((error) =>{
        console.error(error);
      });
  }



답변

Promise를 실행하면 해결되기까지 몇 초가 걸릴 수 있으며 그 때까지 사용자가 앱의 다른 위치로 이동했을 수 있습니다. 따라서 Promise resolves setState가 마운트되지 않은 구성 요소에서 실행 되면 귀하의 경우와 마찬가지로 오류가 발생합니다. 이로 인해 메모리 누수가 발생할 수도 있습니다.

그렇기 때문에 일부 비동기 논리를 구성 요소에서 이동하는 것이 가장 좋습니다.

그렇지 않으면 어떻게 든 약속을 취소해야합니다 . 또는 최후의 수단 (반 패턴)으로 구성 요소가 여전히 마운트되어 있는지 확인하기 위해 변수를 유지할 수 있습니다.

componentDidMount(){
  this.mounted = true;

  this.props.fetchData().then((response) => {
    if(this.mounted) {
      this.setState({ data: response })
    }
  })
}

componentWillUnmount(){
  this.mounted = false;
}

다시 한 번 강조하겠습니다. 이것은 반 패턴 이지만 귀하의 경우에는 충분할 수 있습니다 ( Formik구현 과 마찬가지로 ).

GitHub 에 대한 유사한 토론

편집하다:

이것은 내가와 같은 문제 (필요 아무것도하지만 반작용) 해결하지 얼마나 아마 후크 :

옵션 A :

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

export default function Page() {
  const value = usePromise("https://something.com/api/");
  return (
    <p>{value ? value : "fetching data..."}</p>
  );
}

function usePromise(url) {
  const [value, setState] = useState(null);

  useEffect(() => {
    let isMounted = true; // track whether component is mounted

    request.get(url)
      .then(result => {
        if (isMounted) {
          setState(result);
        }
      });

    return () => {
      // clean up
      isMounted = false;
    };
  }, []); // only on "didMount"

  return value;
}

옵션 B : 또는 useRef클래스의 정적 속성처럼 작동하여 값이 변경 될 때 구성 요소를 다시 렌더링하지 않습니다.

function usePromise2(url) {
  const isMounted = React.useRef(true)
  const [value, setState] = useState(null);


  useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  useEffect(() => {
    request.get(url)
      .then(result => {
        if (isMounted.current) {
          setState(result);
        }
      });
  }, []);

  return value;
}

// or extract it to custom hook:
function useIsMounted() {
  const isMounted = React.useRef(true)

  useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  return isMounted; // returning "isMounted.current" wouldn't work because we would return unmutable primitive
}

예 : https://codesandbox.io/s/86n1wq2z8


답변

React의 친절한 사람들은 취소 가능한 약속으로 가져 오기 호출 / 약속을 포장 할 것을 권장 합니다. 해당 문서에는 가져 오기를 사용하여 코드를 클래스 또는 함수와 별도로 유지하라는 권장 사항이 없지만 다른 클래스 및 함수에이 기능이 필요할 가능성이 높고 코드 중복은 안티 패턴이며 느린 코드와 관계없이 코드를 유지하는 것이 좋습니다. 에서 폐기하거나 취소해야합니다 componentWillUnmount(). React 에 따라 마운트되지 않은 구성 요소에서 상태를 설정하지 않도록 cancel()래핑 된 promise를 호출 할 수 있습니다 componentWillUnmount.

제공된 코드는 React를 가이드로 사용하는 경우 다음 코드 스 니펫과 유사합니다.

const makeCancelable = (promise) => {
    let hasCanceled_ = false;

    const wrappedPromise = new Promise((resolve, reject) => {
        promise.then(
            val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
            error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
        );
    });

    return {
        promise: wrappedPromise,
        cancel() {
            hasCanceled_ = true;
        },
    };
};

const cancelablePromise = makeCancelable(fetch('LINK HERE'));

constructor(props){
    super(props);
    this.state = {
        isLoading: true,
        dataSource: [{
            name: 'loading...',
            id: 'loading',
        }]
    }
}

componentDidMount(){
    cancelablePromise.
        .then((response) => response.json())
        .then((responseJson) => {
            this.setState({
                isLoading: false,
                dataSource: responseJson,
            }, () => {

            });
        })
        .catch((error) =>{
            console.error(error);
        });
}

componentWillUnmount() {
    cancelablePromise.cancel();
}

—- 편집하다 —-

GitHub의 문제에 따라 주어진 답변이 정확하지 않을 수 있음을 발견했습니다. 다음은 내 목적에 맞는 하나의 버전입니다.

export const makeCancelableFunction = (fn) => {
    let hasCanceled = false;

    return {
        promise: (val) => new Promise((resolve, reject) => {
            if (hasCanceled) {
                fn = null;
            } else {
                fn(val);
                resolve(val);
            }
        }),
        cancel() {
            hasCanceled = true;
        }
    };
};

아이디어는 가비지 수집기가 함수를 만들거나 null을 사용하여 메모리를 확보하도록 돕는 것입니다.


답변

AbortController 를 사용 하여 가져 오기 요청을 취소 할 수 있습니다 .

또한보십시오: https://www.npmjs.com/package/abortcontroller-polyfill

class FetchComponent extends React.Component{
  state = { todos: [] };

  controller = new AbortController();

  componentDidMount(){
    fetch('https://jsonplaceholder.typicode.com/todos',{
      signal: this.controller.signal
    })
    .then(res => res.json())
    .then(todos => this.setState({ todos }))
    .catch(e => alert(e.message));
  }

  componentWillUnmount(){
    this.controller.abort();
  }

  render(){
    return null;
  }
}

class App extends React.Component{
  state = { fetch: true };

  componentDidMount(){
    this.setState({ fetch: false });
  }

  render(){
    return this.state.fetch && <FetchComponent/>
  }
}

ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>


답변

게시물이 열렸 기 때문에 “abortable-fetch”가 추가되었습니다.
https://developers.google.com/web/updates/2017/09/abortable-fetch

(문서에서 🙂

컨트롤러 + 신호 기동 AbortController와 AbortSignal을 만나보세요 :

const controller = new AbortController();
const signal = controller.signal;

컨트롤러에는 한 가지 방법 만 있습니다.

controller.abort (); 이렇게하면 다음과 같은 신호를 알립니다.

signal.addEventListener('abort', () => {
  // Logs true:
  console.log(signal.aborted);
});

이 API는 DOM 표준에 의해 제공되며 전체 API입니다. 의도적으로 일반적이므로 다른 웹 표준 및 JavaScript 라이브러리에서 사용할 수 있습니다.

예를 들어, 다음은 5 초 후에 가져 오기 시간 초과를 만드는 방법입니다.

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controller.abort(), 5000);

fetch(url, { signal }).then(response => {
  return response.text();
}).then(text => {
  console.log(text);
});


답변

이 경고의 핵심은 구성 요소에 일부 미해결 콜백 / 약속이 보유한 참조가 있다는 것입니다.

두 번째 패턴에서와 같이 isMounted 상태를 유지하는 (컴포넌트를 활성 상태로 유지하는) 반 패턴을 피하기 위해 react 웹 사이트는 선택적 promise를 사용할 것을 제안 합니다 . 그러나 그 코드는 또한 객체를 살아있게 유지하는 것처럼 보입니다.

대신 setState에 중첩 된 바인딩 함수가있는 클로저를 사용하여 수행했습니다.

여기 내 생성자 (typescript)가 있습니다…

constructor(props: any, context?: any) {
    super(props, context);

    let cancellable = {
        // it's important that this is one level down, so we can drop the
        // reference to the entire object by setting it to undefined.
        setState: this.setState.bind(this)
    };

    this.componentDidMount = async () => {
        let result = await fetch(…);
        // ideally we'd like optional chaining
        // cancellable.setState?.({ url: result || '' });
        cancellable.setState && cancellable.setState({ url: result || '' });
    }

    this.componentWillUnmount = () => {
        cancellable.setState = undefined; // drop all references.
    }
}


답변

“모든 구독을 취소하고 비 동기화”해야 할 때 일반적으로 componentWillUnmount의 redux에 무언가를 발송하여 다른 모든 구독자에게 알리고 필요한 경우 취소에 대한 요청을 하나 더 서버에 보냅니다.


답변

취소에 대해 서버에 알릴 필요가 없다면 가장 좋은 방법은 async / await 구문을 사용하는 것입니다 (사용 가능한 경우).

constructor(props){
  super(props);
  this.state = {
    isLoading: true,
    dataSource: [{
      name: 'loading...',
      id: 'loading',
    }]
  }
}

async componentDidMount() {
  try {
    const responseJson = await fetch('LINK HERE')
      .then((response) => response.json());

    this.setState({
      isLoading: false,
      dataSource: responseJson,
    }
  } catch {
    console.error(error);
  }
}