Kā strādāt ar React pareizo ceļu, lai izvairītos no dažām kļūmēm

Macbook Pro tastatūra

Viena lieta, ko es dzirdu diezgan bieži, ir mūsu jaunā React lietotne “ Let's go for Redux ”. Tas palīdz mērogot, un lietotnes datiem nevajadzētu būt React vietējā stāvoklī, jo tie ir neefektīvi. Vai arī, zvanot uz API un kamēr solījums nav izpildīts, komponents tiek atvienots un tiek parādīta šāda skaista kļūda.

Brīdinājums: Nevar izsaukt setState (vai forceUpdate) nenomontētam komponentam. Tas nav aizliegts, taču tas norāda uz atmiņas noplūdi jūsu lietojumprogrammā. Lai labotu, atceliet visus abonementus un asinhronos uzdevumus metodē componentWillUnmount.

Tātad risinājums, pie kura cilvēki parasti nonāk, ir Redux izmantošana .Man patīk Redukss, un darbs, ko dara Dens Abramovs, ir vienkārši neticami! Šis frants ļoti ilgi šūpojas - es vēlētos, lai es būtu tikpat talantīgs kā viņš.

Bet es esmu pārliecināts, ka tad, kad Dens izgatavoja Redux, viņš mums vienkārši kā palīgu iedeva instrumentu mūsu instrumentu jostā. Tas nav visu rīku džeks. Jūs neizmantojat āmuru, ja jūs varat ieskrūvēt skrūvi ar skrūvgriezi.

Dens pat piekrīt .

Es mīlu React, un pie tā strādāju jau gandrīz divus gadus. Pagaidām nenožēloju. Visu laiku labākais lēmums. Man patīk Vue un visas foršās bibliotēkas / ietvari, kas tur ir. Bet Reakts manā sirdī ieņem īpašu vietu. Tas palīdz man koncentrēties uz darbu, kuru es domāju darīt, nevis izmantot visu laiku DOM manipulācijās. Un tas tiek darīts pēc iespējas labāk un efektīvāk. ar efektīvu izlīgumu.

Dažu pēdējo gadu laikā esmu daudz iemācījies, un esmu pamanījis kopēju problēmu gan jauno, gan pieredzējušo React izstrādātāju vidū: neizmantojiet React pareizi, strādājot ar abonēšanas vai asinhroniem uzdevumiem. Es uzskatu, ka šajā gadījumā dokumentācija, kas tur atrodas, nav labi sagatavota, un tāpēc es nolēmu uzrakstīt šo rakstu.

Vispirms es runāšu par abonementiem, un tad mēs pāriesim pie asinhronas uzdevumu atcelšanas apstrādes, lai izvairītos no atmiņas noplūdēm React (šī raksta galvenais mērķis). Ja tas netiek apstrādāts, tas palēnina mūsu lietotni.

Tagad atgriezīsimies pie šī skaistā kļūdas ziņojuma, par kuru mēs sākotnēji runājām:

Brīdinājums: Nevar izsaukt setState (vai forceUpdate) nenomontētam komponentam. Tas nav aizliegts, taču tas norāda uz atmiņas noplūdi jūsu lietojumprogrammā. Lai labotu, atceliet visus abonementus un asinhronos uzdevumus metodē componentWillUnmount.

Mans mērķis šim rakstam ir pārliecināties, ka nevienam nekad nav jāsaskaras ar šo kļūdu, un nezināt, ko darīt ar to vēlreiz.

Ko mēs aptversim

  • Notīriet abonementus, piemēram, setTimeout / setInterval
  • Notīriet asinhronās darbības, izsaucot XHR pieprasījumu, izmantojot fetchbibliotēkas, piemēram,axios
  • Alternatīvas metodes, daži uzskata, ka citi ir novecojuši.

Pirms sāku, milzīgs kliedziens Kentam C Doddsam , šobrīd foršākajam cilvēkam internetā. Paldies, ka veltījāt laiku un atdevāt sabiedrībai. Viņa Youtube aplādesunegghead kurss par Advanced React Component Pattern ir pārsteidzošs. Pārbaudiet šos resursus, ja vēlaties spert nākamo soli savās React prasmēs.

Es jautāju Kentam par labāku pieeju, lai izvairītos no komplekta atvienošanas no setState , lai es varētu labāk optimizēt React veiktspēju. Viņš devās pāri un tālāk un uzņēma videoklipu par to. Ja esat video veida cilvēks, pārbaudiet to tālāk. Tas sniegs jums soli pa solim detalizētu paskaidrojumu.

Tāpēc tagad sāksim darbu.

1: Notīrīt abonementus

Sāksim ar piemēru:

Parunāsim, kas šeit tikko notika. Uz ko es gribu, lai jūs koncentrētos, ir counter.jsfails, kas pēc 3 sekundēm galvenokārt palielina skaitītāju.

Tas rada kļūdu 5 sekundēs, jo es atvienoju abonementu, to neizdzēšot. Ja vēlaties vēlreiz redzēt kļūdu, vienkārši noklikšķiniet uz pogas Atsvaidzināt CodeSandbox redaktorā, lai redzētu kļūdu konsolē.

Man ir konteinera fails, index.jskas pēc pirmajām piecām sekundēm vienkārši pārslēdz skaitītāja komponentu.

Tātad

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

Savā Index.js es zvanu Counter.js un vienkārši daru to savā renderēšanā:

{showCounter ?  : null}

Tas showCounterir stāvokļa loģiskais skaitlis, kas pēc pirmajām 5 sekundēm iestata vērtību False, tiklīdz komponents ir uzstādīts (componentDidMount).

Patiesība, kas šeit parāda mūsu problēmu, ir counter.jsfails, kas palielina skaitu pēc katrām 3 sekundēm. Tātad pēc pirmajām 3 sekundēm skaitītājs tiek atjaunināts. Bet tiklīdz tas nonāk pie otrā atjauninājuma, kas notiek 6. datumāotrkārt, index.jsfails jau ir atvienojis skaitītāja komponentu 5. vietāotrais. Līdz brīdim, kad skaitītāja komponents ir sasniedzis 6. vietuotrkārt, tas otro reizi atjaunina skaitītāju.

Tas atjaunina savu stāvokli, bet tad šeit ir problēma. Skaitītāja komponentam nav DOM, lai atjauninātu stāvokli, un tas ir, kad React rada kļūdu. Šī skaistā kļūda, kuru mēs apspriedām iepriekš:

Brīdinājums: Nevar izsaukt setState (vai forceUpdate) nenomontētam komponentam. Tas nav aizliegts, taču tas norāda uz atmiņas noplūdi jūsu lietojumprogrammā. Lai labotu, atceliet visus abonementus un asinhronos uzdevumus metodē componentWillUnmount.

Tagad, ja jūs esat jauns React lietotājs, jūs varētu teikt: "Nu Adeel ... jā, bet vai mēs 5. sekundē vienkārši neatvienojām skaitītāja komponentu? Ja skaitītājam nav komponenta, kā tā stāvokli var atjaunināt sestajā sekundē? "

Jā tev ir taisnība. Bet, kad mēs darām kaut ko līdzīgu setTimeoutvai setIntervalsavos React komponentos, tas nav atkarīgs vai saistīts ar mūsu React klasi, kā jūs domājat, ka tā varētu būt. Tas turpinās darboties pēc noteiktā stāvokļa, ja vien neatcelsiet abonementu.

Tagad jūs, iespējams, jau to darāt, kad jūsu nosacījums ir izpildīts. Bet ko tad, ja jūsu nosacījums vēl nav izpildīts un lietotājs nolemj mainīt lapas, kur šī darbība joprojām notiek?

Labākais veids, kā dzēst šāda veida abonementus, ir jūsu componentWillUnmountdzīves cikls. Šeit ir piemērs, kā jūs to varat izdarīt. Pārbaudiet counter.js faila komponentuWillUnmount metodi:

Un tas ir diezgan piemērots setTimout& setInterval.

2: API (XHR) pārtraukumi

  • Neglīti vecā pieeja (novecojusi)
  • Laba jaunāka pieeja (šī raksta galvenais mērķis)

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.

Ar šo pieeju darbs tiek paveikts, taču, kā saka React dokumenti:

Galvenais lietošanas gadījums isMounted()ir izvairīties no zvanīšanas setState()pēc komponenta atvienošanas, jo, zvanot setState()pēc komponenta atvienošanas, tiks parādīts brīdinājums. Brīdinājums “setState” pastāv, lai palīdzētu uztvert kļūdas, jo nepiesaistīta setState()komponenta izsaukšana ir norāde, ka jūsu lietotne / komponents kaut kā nav pareizi notīrīts. Konkrēti, setState()nepiesaistīta komponenta izsaukšana nozīmē, ka jūsu lietotne joprojām tur atsauci uz komponentu pēc komponenta atvienošanas - kas bieži norāda uz atmiņas noplūdi! Lasīt vairāk …

Tas nozīmē, ka, lai gan mēs esam izvairījušies no nevajadzīgas setState, atmiņa joprojām nav iztīrīta. Joprojām notiek asinhrona darbība, kas nezina, ka komponenta dzīves cikls ir beidzies un tas vairs nav vajadzīgs.

Parunāsim par pareizo ceļu

Lai saglabātu dienu, ir AbortControllers . Saskaņā ar MDN dokumentāciju tajā teikts:

AbortControllerInterfeiss pārstāv kontrolieris objektu, kas ļauj pārtraukt vienu vai vairākas DOM pieprasījumus, kā un kad nepieciešams. Lasīt vairāk ..

Apskatīsim šeit mazliet dziļāk. Ar kodu, protams, jo visi ❤ kodi.

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.

Ja vēlaties izlasīt vēl plašāku piemēru AbortController, MDN foršajiem ļaudīm viņu Github ir šis patiešām jauks un elegants piemērs. To var pārbaudīt šeit.

Es gribēju runāt par šiem abortu pieprasījumiem tāpēc, ka maz cilvēku par tiem zina. Pieprasījums pēc atcelšanas sākās 2015. gadā. Šeit ir oriģināls GitHub izdevums Abort - tas beidzot ieguva atbalstu aptuveni 2017. gada oktobrī. Tā ir divu gadu starpība. Oho! Ir dažas bibliotēkas, piemēram, axios, kas atbalsta AbortController. Es apspriedīšu, kā jūs to varat izmantot ar axios, bet vispirms es gribēju parādīt padziļinātu versiju, kā darbojas AbortController.

XHR pieprasījuma atcelšana Axios

"Dariet vai nedariet. Nav mēģinājuma. ” - Joda

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.

Paldies, ka veltījāt laiku lasīšanai. Uzsauciet manam ļoti talantīgajam kolēģim Kinanam, ka viņš man palīdzēja izlasīt šo rakstu. Paldies Kent C Dodds par iedvesmu JavaScript OSS kopienā.

Es atkal gribētu dzirdēt jūsu atsauksmes par to. Jūs vienmēr varat mani sazināties ar čivināt .

Ir arī vēl viens pārsteidzošs lasījums Abort Controller, kuru es atradu caur Jake Archibald MDN dokumentāciju . Es iesaku jums to izlasīt, ja jums ir tāds zinātkārs raksturs kā manējais.