Kaip dirbti su „Reaguokite“ teisingai, kad išvengtumėte įprastų spąstų

„Macbook Pro“ klaviatūra

Vienas dalykas, kurį girdžiu gana dažnai, yra „ Let's go for Redux “ mūsų naujojoje „React“ programoje. Tai padeda jums keisti mastelį, o programos duomenys neturėtų būti „React“ vietinėje būsenoje, nes jie yra neefektyvūs. Arba, kai skambinate API ir kol laukia pažadas, komponentas bus atjungtas ir gausite šią gražią klaidą.

Įspėjimas: Negalima iškviesti „setState“ (arba „forceUpdate“) ant nemontuoto komponento. Tai draudimas, tačiau tai rodo atminties nutekėjimą jūsų programoje. Norėdami išspręsti, atšaukite visas prenumeratas ir asinchronines užduotis, atlikdami metodą „komponentoWillUnmount“.

Taigi sprendimas, kurį žmonės dažniausiai pasiekia, yra „ Redux“ naudojimas .Man patinka Reduxas, o darbas, kurį dirba Danas Abramovas, yra tiesiog neįtikėtinas! Tas bičiulis labai ilgai rokuoja - norėčiau, kad būčiau toks pat talentingas kaip jis.

Bet esu įsitikinęs, kad kai Danas gamino „Redux“, jis tiesiog padėjo mums įrankį mūsų įrankių dirže. Tai ne visų įrankių lizdas. Nenaudojate plaktuko, kai varžtą galite įsukti varžtu.

Danas netgi sutinka .

Aš myliu „React“ ir dirbu prie jo jau beveik dvejus metus. Kol kas nesigailiu. Geriausias sprendimas. Man patinka „Vue“ ir visos šaunios bibliotekos / sistemos. Bet „React“ mano širdyje užima ypatingą vietą. Tai padeda man sutelkti dėmesį į darbą, kurį, manau, turėčiau atlikti, o ne skirti visą laiką DOM manipuliacijoms. Tai daro kuo geriau ir efektyviau. su veiksmingu susitaikymu.

Per pastaruosius kelerius metus daug ko išmokau ir pastebėjau bendrą problemą tarp naujų ir patyrusių „React“ kūrėjų: nenaudokite „React“ teisingai, kai sprendžiate prenumeratos ar asinchronines užduotis. Manau, kad dokumentai šiuo atveju nėra tinkamai pateikti, todėl nusprendžiau parašyti šį straipsnį.

Pirmiausia pakalbėsiu apie prenumeratas, o tada pereisime prie asinchroninio užduočių atšaukimo tvarkymo, kad išvengtume atminties nutekėjimo „React“ (pagrindinis šio straipsnio tikslas). Jei tai nebus tvarkoma, tai sulėtins mūsų programą.

Dabar grįžkime prie gražaus klaidos pranešimo, apie kurį iš pradžių kalbėjome:

Įspėjimas: Negalima iškviesti „setState“ (arba „forceUpdate“) ant nemontuoto komponento. Tai draudimas, tačiau tai rodo atminties nutekėjimą jūsų programoje. Norėdami išspręsti, atšaukite visas prenumeratas ir asinchronines užduotis, atlikdami metodą „komponentoWillUnmount“.

Mano tikslas šiame straipsnyje yra įsitikinti, kad niekam niekada neteks susidurti su šia klaida ir nežinoti, ką daryti toliau.

Ką apimsime

  • Išvalykite prenumeratas, pvz., „SetTimeout“ / „setInterval“
  • Išvalykite asinchroninius veiksmus, kai skambinate į XHR užklausą naudodamiesi fetchtokiomis bibliotekomis kaipaxios
  • Alternatyvūs metodai, kai kurie mano, kad kiti nebeveikia.

Prieš pradedant, didžiulis sušukimas Kentui C Doddsui , šauniausiam interneto vartotojui šiuo metu. Dėkojame, kad skyrėte laiko ir atidavėte bendruomenei. Jo Youtube podcastyirKiaušinių galvos kursas apie pažangius reaguojančių komponentų modelius yra nuostabus. Patikrinkite šiuos išteklius, jei norite žengti kitą žingsnį savo „React“ įgūdžių srityje.

Aš paklausiau Kento apie geresnį metodą, kad būtų išvengta „ setState “ komponentų atjungimo , kad galėčiau geriau optimizuoti „React“ našumą. Jis nuėjo aukščiau ir anapus ir padarė jame vaizdo įrašą. Jei esate vaizdo įrašo tipo asmuo, patikrinkite jį žemiau. Tai suteiks jums žingsnis po žingsnio ir išsamiai paaiškins.

Taigi dabar pradėkime.

1: Išvalyti prenumeratas

Pradėkime nuo pavyzdžio:

Pakalbėkime, kas čia ką tik įvyko. Norėčiau, kad sutelktumėte dėmesį į counter.jsfailą, kuris iš esmės padidina skaitiklį po 3 sekundžių.

Tai suteikia klaidą per 5 sekundes, nes atjungiau prenumeratą jos nepašalinęs. Jei norite dar kartą pamatyti klaidą, tiesiog paspauskite atnaujinimo mygtuką „CodeSandbox“ redaktoriuje, kad pamatytumėte klaidą konsolėje.

Turiu savo konteinerio failą, index.jskuris tiesiog perjungia skaitiklio komponentą po pirmųjų penkių sekundžių.

Taigi

- - - → Index.js— - - - → Counter.js

„Index.js“ paskambinu į „Counter.js“ ir tiesiog tai padarau pateikdamas:

{showCounter ?  : null}

Tai showCounteryra būsenos loginė reikšmė, kuri po pirmųjų 5 sekundžių, kai tik komponentas sumontuojamas, nustato save klaidingai (componentDidMount).

Tikras dalykas, iliustruojantis mūsų problemą, yra counter.jsfailas, kuris skaičiavimą didina kas 3 sekundes. Taigi po pirmųjų 3 sekundžių skaitiklis atnaujinamas. Bet kai tik pateksite į antrą atnaujinimą, kuris įvyksta 6-ajameantra, index.jsfailas jau atjungė skaitiklio komponentą 5-ajameantra. Kai skaitiklio komponentas pasieks 6-tą vietąantra, jis antrą kartą atnaujina skaitiklį.

Ji atnaujina savo būseną, bet tada yra problema. Nėra DOM, kad skaitiklio komponentas atnaujintų būseną, ir tada, kai „React“ išmeta klaidą. Ši graži klaida, kurią aptarėme aukščiau:

Įspėjimas: Negalima iškviesti „setState“ (arba „forceUpdate“) ant nemontuoto komponento. Tai draudimas, tačiau tai rodo atminties nutekėjimą jūsų programoje. Norėdami išspręsti, atšaukite visas prenumeratas ir asinchronines užduotis, atlikdami metodą „komponentoWillUnmount“.

Dabar, jei esate naujas „React“ vartotojas, galite sakyti: „Na, Adeel ... taip, bet ar mes ne tik 5 sekundę atjungėme skaitiklio komponentą? Jei nėra skaitiklio komponento, kaip jo būsena vis tiek atnaujinama šeštą sekundę? “

Taip, tu teisus. Bet kai mes darome kažką panašaus setTimeoutarba setIntervalmūsų reaguoti komponentų, tai nepriklauso nuo ar susiję su mūsų reaguoti klasės, kaip jūs manote, kad tai gali būti. Pasibaigus nurodytai būklei, ji veiks tol, kol neatšauksite jos prenumeratos.

Dabar jūs jau galite tai daryti, kai jūsų sąlyga yra įvykdyta. Bet ką daryti, jei jūsų sąlyga dar neįvykdyta ir vartotojas nusprendžia pakeisti puslapius, kuriuose vis dar vyksta šis veiksmas?

Geriausias būdas išvalyti tokio tipo prenumeratas yra jūsų componentWillUnmountgyvenimo ciklas. Štai pavyzdys, kaip galite tai padaryti. Patikrinkite failo counter.js komponentąWillUnmount metodas:

Ir beveik tai yra skirta setTimout& setInterval.

2: API (XHR) nutraukimai

  • Bjaurus senas požiūris (nebenaudojamas)
  • Geras naujesnis požiūris (pagrindinis šio straipsnio tikslas)

So, we’ve discussed subscriptions. But what if you make an asynchronous request? How do you cancel it?

The old way

Before I talk about that, I want to talk about a deprecated method in React called isMounted()

Before December 2015, there was a method called isMounted in React. You can read more about it in the React blog. What it did was something like this:

import React from 'react' import ReactDOM from 'react-dom' import axios from 'axios' class RandomUser extends React.Component { state = {user: null} _isMounted = false handleButtonClick = async () => { const response = await axios.get('//randomuser.me/api/') if (this._isMounted) { this.setState({ user: response.data }) } } componentDidMount() { this._isMounted = true } componentWillUnmount() { this._isMounted = false } render() { return ( Click Me 
{JSON.stringify(this.state.user, null, 2)}
) } }

For the purpose of this example, I am using a library called axios for making an XHR request.

Let’s go through it. I initially set this_isMounted to false right next to where I initialized my state. As soon as the life cycle componentDidMount gets called, I set this._isMounted to true. During that time, if an end user clicks the button, an XHR request is made. I am using randomuser.me. As soon as the promise gets resolved, I check if the component is still mounted with this_isMounted. If it’s true, I update my state, otherwise I ignore it.

The user might clicked on the button while the asynchronous call was being resolved. This would result in the user switching pages. So to avoid an unnecessary state update, we can simply handle it in our life cycle method componentWillUnmount. I simply set this._isMounted to false. So whenever the asynchronous API call gets resolved, it will check if this_isMounted is false and then it will not update the state.

Šis metodas tikrai padaro darbą, tačiau, kaip sakoma „React“ dokumentuose:

Pagrindinis naudojimo atvejis isMounted()yra vengti skambučio setState()atjungus komponentą, nes paskambinus setState()komponentui atjungus, bus paskelbtas įspėjimas. „SetState“ įspėjimas yra skirtas padėti jums užfiksuoti klaidas, nes iškvietimas setState()neįdėtam komponentui rodo, kad jūsų programa / komponentas kažkaip nepavyko tinkamai išvalyti. Konkrečiau, iškvietus setState()neuždėtą komponentą, jūsų programa vis dar turi nuorodą į komponentą, kai komponentas bus atjungtas - o tai dažnai rodo atminties nutekėjimą! Skaityti daugiau …

Tai reiškia, kad nors ir išvengėme nereikalingo „setState“, atmintis vis tiek neišvalyta. Vis dar vyksta asinchroninis veiksmas, kuris nežino, kad komponento gyvavimo ciklas baigėsi ir jo nebereikia.

Pakalbėkime apie teisingą kelią

Norėdami išsaugoti dieną, čia yra „ AbortControllers“ . Kaip nurodyta MDN dokumentuose, jame teigiama:

AbortControllerSąsaja reiškia valdiklio objektą, kad leidžia nutraukti vieną ar daugiau DOM prašymus, kaip ir kada pageidaujama. Skaityti daugiau ..

Pažvelkime čia kiek giliau. Su kodu, žinoma, nes visi ❤ kodą.

var myController = new AbortController(); var mySignal = myController.signal; var downloadBtn = document.querySelector('.download'); var abortBtn = document.querySelector('.abort'); downloadBtn.addEventListener('click', fetchVideo); abortBtn.addEventListener('click', function() { myController.abort(); console.log('Download aborted'); }); function fetchVideo() { ... fetch(url, { signal: mySignal }).then(function(response) { ... }).catch(function(e) { reports.textContent = 'Download error: ' + e.message; }) } 

First we create a new AbortController and assign it to a variable called myController. Then we make a signal for that AbortController. Think of the signal as an indicator to tell our XHR requests when it’s time to abort the request.

Assume that we have 2 buttons, Download and Abort . The download button downloads a video, but what if, while downloading, we want to cancel that download request? We simply need to call myController.abort(). Now this controller will abort all requests associated with it.

How, you might ask?

After we did var myController = new AbortController() we did this var mySignal = myController.signal . Now in my fetch request, where I tell it the URL and the payload, I just need to pass in mySignal to link/signal that FETCh request with my awesome AbortController.

Jei norite perskaityti dar platesnį pavyzdį AbortController, šaunūs MDN žmonės turi šį tikrai gražų ir elegantišką pavyzdį savo „Github“. Galite tai patikrinti čia.

Norėjau pakalbėti apie šiuos prašymus nutraukti, nes nedaugelis žmonių apie juos žino. Prašymas nutraukti atsiėmimą prasidėjo 2015 m. Štai originalus „GitHub“ leidimas nutraukti - jis pagaliau sulaukė palaikymo apie 2017 m. Spalio mėn. Tai yra dvejų metų skirtumas. Oho! Yra keletas bibliotekų, tokių kaip axios, kurios palaiko „AbortController“. Aptarsiu, kaip galite jį naudoti su axios, bet pirmiausia norėjau parodyti išsamią „AbortController“ veikimo versiją po dangčiu.

XHR užklausos atšaukimas „Axios“

„Daryk arba ne. Nebandoma. “ - Yoda

The implementation I talked about above isn’t specific to React, but that’s what we’ll discuss here. The main purpose of this article is to show you how to clear unnecessary DOM manipulations in React when an XHR request is made and the component is unmounted while the request is in pending state. Whew!

So without further ado, here we go.

import React, { Component } from 'react'; import axios from 'axios'; class Example extends Component { signal = axios.CancelToken.source(); state = { isLoading: false, user: {}, } componentDidMount() { this.onLoadUser(); } componentWillUnmount() { this.signal.cancel('Api is being canceled'); } onLoadUser = async () => { try { this.setState({ isLoading: true }); const response = await axios.get('//randomuser.me/api/', { cancelToken: this.signal.token, }) this.setState({ user: response.data, isLoading: true }); } catch (err) { if (axios.isCancel(err)) { console.log('Error: ', err.message); // => prints: Api is being canceled } else { this.setState({ isLoading: false }); } } } render() { return ( 
{JSON.stringify(this.state.user, null, 2)}
) } }

Let’s walk through this code

I set this.signal to axios.CancelToken.source()which basically instantiates a new AbortController and assigns the signal of that AbortController to this.signal. Next I call a method in componentDidMount called this.onLoadUser() which calls a random user information from a third party API randomuser.me. When I call that API, I also pass the signal to a property in axios called cancelToken

The next thing I do is in my componentWillUnmount where I call the abort method which is linked to that signal. Now let’s assume that as soon as the component was loaded, the API was called and the XHR request went in a pending state.

Now, the request was pending (that is, it wasn’t resolved or rejected but the user decided to go to another page. As soon as the life cycle method componentWillUnmount gets called up, we will abort our API request. As soon as the API get’s aborted/cancelled, the promise will get rejected and it will land in the catch block of that try/catch statement, particularly in the if (axios.isCancel(err) {} block.

Now we know explicitly that the API was aborted, because the component was unmounted and therefore logs an error. But we know that we no longer need to update that state since it is no longer required.

P.S: You can use the same signal and pass it as many XHR requests in your component as you like. When the component gets un mounted, all those XHR requests that are in a pending state will get cancelled when componentWillUnmount is called.

Final details

Congratulations! :) If you have read this far, you’ve just learned how to abort an XHR request on your own terms.

Let’s carry on just a little bit more. Normally, your XHR requests are in one file, and your main container component is in another (from which you call that API method). How do you pass that signal to another file and still get that XHR request cancelled?

Here is how you do it:

import React, { Component } from 'react'; import axios from 'axios'; // API import { onLoadUser } from './UserAPI'; class Example extends Component { signal = axios.CancelToken.source(); state = { isLoading: false, user: {}, } componentDidMount() { this.onLoadUser(); } componentWillUnmount() { this.signal.cancel('Api is being canceled'); } onLoadUser = async () => { try { this.setState({ isLoading: true }); const data = await onLoadUser(this.signal.token); this.setState({ user: data, isLoading: true }); } catch (error) { if (axios.isCancel(err)) { console.log('Error: ', err.message); // => prints: Api is being canceled } else { this.setState({ isLoading: false }); } } } render() { return ( 
{JSON.stringify(this.state.user, null, 2)}
) } }; }
export const onLoadUser = async myCancelToken => { try { const { data } = await axios.get('//randomuser.me/api/', { cancelToken: myCancelToken, }) return data; } catch (error) { throw error; } }; 

I hope this has helped you and I hope you’ve learned something. If you liked it, please give it some claps.

Ačiū, kad skyrėte laiko skaityti. Šūkis mano labai talentingam kolegai Kinanui , kuris padėjo man įrodymą perskaityti šį straipsnį. Ačiū Kent C Dodds už tai, kad įkvėpė „JavaScript“ OSS bendruomenėje.

Vėlgi, norėčiau išgirsti jūsų atsiliepimus apie tai. Visada galite susisiekti su manimi „ Twitter“ tinkle .

Taip pat yra dar vienas nuostabus „ Abort Controller“ skaitymas , kurį radau per MDN dokumentaciją, kurį pateikė Jake'as Archibaldas . Siūlau jums perskaityti, jei turite įdomų pobūdį, tokį kaip aš.