कैसे एक घटक रद्द करने के लिए परपुनःउपकरण


90

मैं सोचता हूँ कि शीर्षक यह सब कहता है। पीली चेतावनी हर बार प्रदर्शित की जाती है जब मैं एक घटक को खोल देता हूं जो अभी भी प्राप्त कर रहा है।

कंसोल

चेतावनी: अनमाउंट किए गए घटक पर कॉल 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);
      });
  }

क्या यह चेतावनी है कि मेरे पास यह मुद्दा नहीं है
नीमा मोरदी

प्रश्न अपडेट किया गया
João Belo

क्या आपने वादा किया था या भ्रूण के लिए async कोड
nima moradi

आप को क्वेश्चन में लाने के लिए कोड जोड़ें
nima moradi

जवाबों:


80

जब आप एक वादा करते हैं, तो इसे हल होने से पहले कुछ सेकंड लग सकते हैं और उस समय तक उपयोगकर्ता आपके ऐप में किसी अन्य स्थान पर नेविगेट कर सकता है। इसलिए जब वादा हल किया 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;
}

विकल्प बी: वैकल्पिक रूप से 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


4
तो वहाँ कोई वास्तविक तरीका नहीं है के लिए घटक को रद्द करेंWillUnmount?
जोओ बेलो

1
ओह, मैंने पहले आपके उत्तर के कोड को नोटिस नहीं किया, यह काम किया। साभार
जोओ बेलो


2
आपके लिए "घटकों से बाहर अपने अतुल्यकालिक तर्क को स्थानांतरित करने के लिए सबसे अच्छा क्यों है?" एक घटक प्रतिक्रिया में सब कुछ नहीं है?
कारपिक

1
@ टॉमाज़ मूलारज़ीक आपको बहुत धन्यवाद, आपने योग्य सामान किया।
KARTHIKEYAN.A

25

प्रतिक्रियाशील लोगों के अनुकूल लोग रद्द किए गए वादे में आपके कॉल्स / वादों को लपेटने की सलाह देते हैं । हालांकि उस दस्तावेज़ में कोई सिफारिश नहीं है कि कोड को भ्रूण के साथ वर्ग या फ़ंक्शन से अलग रखने के लिए, यह उचित लगता है क्योंकि अन्य वर्गों और कार्यों को इस कार्यक्षमता की आवश्यकता होती है, कोड दोहराव एक विरोधी पैटर्न है, और लिंचिंग कोड की परवाह किए बिना। में निपटाया या रद्द किया जाना चाहिए componentWillUnmount()। प्रतिक्रिया के अनुसार, आप किसी अनमाउंट किए गए कंपोनेंट पर स्टेट सेट करने से बचने के cancel()लिए लिपटे वादे पर कॉल कर सकते componentWillUnmountहैं।

यदि हम एक गाइड के रूप में प्रतिक्रिया का उपयोग करते हैं तो प्रदान की गई कोड इन कोड स्निपेट की तरह कुछ दिखाई देगा:

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;
        }
    };
};

यह विचार था कि फंक्शन या जो भी आप शून्य का उपयोग करते हैं, कचरा कलेक्टर को स्मृति मुक्त करने में मदद करें।


क्या आपके पास जीथब पर मुद्दे का लिंक है
Ren

@, पृष्ठ को संपादित करने और मुद्दों पर चर्चा करने के लिए एक GitHub साइट है
हेलोन्ज

मुझे अब यकीन नहीं है कि उस GitHub परियोजना पर सटीक मुद्दा कहां है।
हैलोनज

1
GitHub मुद्दे से लिंक: github.com/facebook/react/issues/5465
sammalfix

22

आप लाने के अनुरोध को रद्द करने के लिए 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>


2
काश मुझे पता होता कि AbortController जैसे अनुरोधों को रद्द करने के लिए एक वेब एपीआई है। लेकिन ठीक है, इसे जानने में देर नहीं हुई है। धन्यवाद।
लेक्स सॉफ्ट

11

चूंकि पोस्ट खोला गया था, इसलिए एक "गर्भपात-भ्रूण" जोड़ा गया है। 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 है। यह जानबूझकर सामान्य है इसलिए इसका उपयोग अन्य वेब मानकों और जावास्क्रिप्ट पुस्तकालयों द्वारा किया जा सकता है।

उदाहरण के लिए, यहां बताया गया है कि आप 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);
});

दिलचस्प है, मैं इस तरह से कोशिश करूंगा। लेकिन इससे पहले, मैं पहले AbortController API पढ़ूंगा।
लेक्स सॉफ्ट

क्या हम ऐसे कई भ्रूणों के लिए सिर्फ एक एबोर्टकंट्रोलर इंस्टेंस का उपयोग कर सकते हैं, जब हम इस एकल एबोर्टकंट्रोलर के कंपोनेंट मेथड को कंप्लीटवर्लवुमाउंट में इनवाइट करते हैं, तो यह हमारे कंपोनेंट में मौजूद सभी फीलिंग्स को रद्द कर देगा? यदि नहीं, तो इसका मतलब है कि हमें प्रत्येक गर्भस्थ शिशु के लिए अलग-अलग एबॉर्टकंट्रोलर इंस्टेंस उपलब्ध कराना होगा, है ना?
लेक्स सॉफ्ट

3

इस चेतावनी का सार यह है कि आपके घटक के पास इसका संदर्भ है जो कुछ बकाया कॉलबैक / वादे के द्वारा होता है।

अपने सम्‍मिलित स्थिति को बनाए रखने की एंटीपैटर्न से बचने के लिए (जो आपके घटक को जीवित रखता है) जैसा कि दूसरे पैटर्न में किया गया था, प्रतिक्रिया वेबसाइट एक वैकल्पिक वादा का उपयोग करने का सुझाव देती है ; हालाँकि वह कोड आपकी वस्तु को जीवित रखने के लिए भी दिखाई देता है।

इसके बजाय, मैंने इसे सेट करने के लिए नेस्टेड बाउंड फ़ंक्शन के साथ एक क्लोजर का उपयोग करके किया है।

यहाँ मेरा निर्माता (टाइपस्क्रिप्ट) है ...

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.
    }
}

3
यह वैचारिक रूप से एक अलग ध्वज रखने की तुलना में अलग नहीं है, केवल आप इसे बंद करने के बजाय इसे बंद करने के लिए बाध्य कर रहे हैंthis
अनिलरेडिफ्ट

2

जब मुझे "सभी सदस्यता और अतुल्यकालिक को रद्द करने की आवश्यकता होती है" तो मैं आमतौर पर घटक में फिर से कुछ करने के लिए भेज देता हूं। अन्य सभी ग्राहकों को सूचित करने और यदि आवश्यक हो तो सर्वर को रद्द करने के बारे में एक और अनुरोध भेजने के लिए।


2

मुझे लगता है कि अगर सर्वर को रद्द करने के बारे में सूचित करना आवश्यक नहीं है - सबसे अच्छा तरीका सिर्फ एसिंक्स / वेट सिंटैक्स का उपयोग करना है (यदि यह उपलब्ध है)।

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);
  }
}

0

रद्द किए गए वादे के अलावा स्वीकृत समाधान में उदाहरणों के अलावा, useAsyncCallbackएक अनुरोध कॉलबैक में हुक लपेटना और रद्द करने योग्य वादा वापस करना आसान हो सकता है । विचार समान है, लेकिन एक हुक के साथ एक नियमित रूप से काम कर रहा है useCallback। यहाँ कार्यान्वयन का एक उदाहरण है:

function useAsyncCallback<T, U extends (...args: any[]) => Promise<T>>(callback: U, dependencies: any[]) {
  const isMounted = useRef(true)

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

  const cb = useCallback(callback, dependencies)

  const cancellableCallback = useCallback(
    (...args: any[]) =>
      new Promise<T>((resolve, reject) => {
        cb(...args).then(
          value => (isMounted.current ? resolve(value) : reject({ isCanceled: true })),
          error => (isMounted.current ? reject(error) : reject({ isCanceled: true }))
        )
      }),
    [cb]
  )

  return cancellableCallback
}

-2

मुझे लगता है कि मुझे इसके बारे में एक तरीका सूझा। समस्या उतनी ही नहीं है जितनी कि खुद की, लेकिन घटक के ख़ारिज होने के बाद सेटस्टैट की है। तो समाधान के this.state.isMountedरूप में सेट करने के लिए किया गया था falseऔर फिर componentWillMountइसे बदलने के लिए सच है, और componentWillUnmountफिर से झूठे करने के लिए सेट में । फिर बस if(this.state.isMounted)सेट के अंदर लाने के लिए। इस तरह:

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

  componentDidMount(){
    this.setState({
      isMounted: true,
    })

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

  componentWillUnmount() {
    this.setState({
      isMounted: false,
    })
  }

3
setState शायद आदर्श नहीं है, क्योंकि यह राज्य में मूल्य को तुरंत अपडेट नहीं करेगा।
लियोनफ़
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.