Како радити са Реацт-ом на прави начин да бисте избегли неке уобичајене замке

Мацбоок Про тастатура

Често чујем нешто „ Идемо за Редук “ у нашој новој апликацији Реацт. Помаже вам у скалирању, а подаци апликације не би требало да буду у локалном стању Реацт, јер су неефикасни. Или када позовете АПИ и док је обећање на чекању, компонента се демонтира и добијате следећу лепу грешку.

Упозорење: Не могу позвати сетСтате (или форцеУпдате) на демонтираној компоненти. Ово није дозвољено, али указује на цурење меморије у вашој апликацији. Да бисте то поправили, откажите све претплате и асинхроне задатке у методи цомпонентВиллУнмоунт.

Дакле, решење до којег људи обично долазе је коришћење Редук-а .Волим Редук и посао који ради Дан Абрамов је једноставно невероватан! Тај фрајер се јако љуља - волео бих да сам и ја пола талентован као и он.

Али сигуран сам да нам је Дан када је правио Редук само давао алат у појасу за алат као помоћника. Није дизалица свих алата. Не користите чекић када вијак можете заврнути одвијачем.

Дан се чак слаже .

Волим Реацт и на њему радим већ скоро две године. За сада се не кајем. Најбоља одлука икад. Свиђа ми се Вуе и сва кул библиотека / оквири тамо. Али Реацт заузима посебно место у мом срцу. Помаже ми да се усредсредим на посао који бих требало да радим, а не да заузимам сво своје време у ДОМ манипулацијама. И то чини на најбољи и најефикаснији могући начин. са својим ефикасним помирењем.

Током протеклих неколико година научио сам много тога и приметио сам чест проблем међу новим и искусним програмерима Реацт-а: не коришћење Реацт-а на прави начин када се баве претплатом или асинхроним задацима. Мислим да документација тамо није добро постављена у овом случају, и зато сам одлучила да напишем овај чланак.

Прво ћу говорити о претплатама, а затим ћемо прећи на руковање асинхроним отказивањем задатака како бисмо избегли цурење меморије у Реацт-у (главна сврха овог чланка). Ако се не реши, ово успорава нашу апликацију.

Сада се вратимо на ону лепу поруку о грешци о којој смо првобитно разговарали:

Упозорење: Не могу позвати сетСтате (или форцеУпдате) на демонтираној компоненти. Ово није дозвољено, али указује на цурење меморије у вашој апликацији. Да бисте то поправили, откажите све претплате и асинхроне задатке у методи цомпонентВиллУнмоунт.

Мој циљ за овај чланак је да се уверим да се нико више никада неће суочити са овом грешком и да не зна шта поново да уради.

Шта ћемо покрити

  • Обришите претплате попут сетТимеоут / сетИнтервал
  • Обришите асинхроне радње када позовете КСХР захтев помоћу fetchили попут библиотекаaxios
  • Алтернативне методе, неки незадовољни другима застарели.

Пре него што започнем, узвикујем Кент Ц Доддс , најслађу особу на Интернету тренутно. Хвала вам што сте одвојили време и вратили заједницу. Његови Иоутубе подкастовиикурс јајашца на Адванцед Реацт Цомпонент Паттернима је невероватан. Погледајте ове ресурсе ако желите да направите следећи корак у својим Реацт вештинама.

Питао сам Кента за бољи приступ како бих избегао сетСтате на демонтажи компоненте како бих могао боље да оптимизујем перформансе Реацт-а. Прешао је и даље и направио видео о томе. Ако сте видео особа, погледајте доле. Пружиће вам корак по корак са детаљним објашњењем.

Дакле, хајде да ускочимо да започнемо.

1: Обриши претплате

Почнимо са примером:

Хајде да разговарамо о томе шта се управо овде догодило. Оно на шта желим да се усредсредите је counter.jsдатотека која у основи повећава бројач након 3 секунде.

Ово даје грешку за 5 секунди, јер сам демонтирао претплату, а да је нисам обрисао. Ако желите поново да видите грешку, само притисните дугме за освежавање у уређивачу ЦодеСандбок да бисте видели грешку у конзоли.

Имам своју датотеку контејнера index.jsкоја једноставно пребацује компоненту бројача након првих пет секунди.

Тако

- - - → Индек.јс— - - - → Цоунтер.јс

У свом Индек.јс зовем Цоунтер.јс и једноставно то радим у свом рендеру:

{showCounter ?  : null}

То showCounterје логичка вредност стања која се сама поставила на фалсе након првих 5 секунди чим се компонента монтира (цомпонентДидМоунт).

Права ствар која илуструје наш проблем овде је counter.jsдатотека која повећава број након сваке 3 секунде. Дакле, након прве 3 секунде, бројач се ажурира. Али чим дође до другог ажурирања, које се дешава 6. мајадруго, index.jsдатотека је 5. демонтирала компоненту бројачадруго. Док компонента бројача достигне 6. местодруго, по други пут ажурира бројач.

Ажурира своје стање, али онда је ту проблем. Не постоји ДОМ за компоненту бројача да би ажурирао стање и тада Реацт баца грешку. Та лепа грешка о којој смо горе разговарали:

Упозорење: Не могу позвати сетСтате (или форцеУпдате) на демонтираној компоненти. Ово није дозвољено, али указује на цурење меморије у вашој апликацији. Да бисте то поправили, откажите све претплате и асинхроне задатке у методи цомпонентВиллУнмоунт.

Сад ако сте нови у Реацт-у, могли бисте рећи, „па Адеел ... да, али зар нисмо управо демонтирали компоненту Цоунтер у 5. секунди? Ако не постоји компонента за бројач, како се стање још увек може ажурирати у шестој секунди? “

Да, ти си у праву. Али када радимо нешто попут setTimeoutили setIntervalу нашим Реацт компонентама, то није зависно од наше Реацт класе нити је повезано са њом као што мислите да може бити. Наставиће да ради и након наведеног стања, осим док или док не откажете претплату.

Сада то можда већ радите када су ваши услови испуњени. Али шта ако ваш услов још није испуњен и корисник одлучи да промени странице на којима се ова акција још увек догађа?

Најбољи начин да очистите ове врсте претплата је у вашем componentWillUnmountживотном циклусу. Ево примера како то можете учинити. Погледајте методу цомпонентВиллУнмоунт датотеке цоунтер.јс:

И то је прилично за setTimout& setInterval.

2: Прекиди АПИ (КСХР) прекида

  • Ружни стари приступ (застарео)
  • Добри новији приступ (главна сврха овог чланка)

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.

Овај приступ посао заиста завршава, али како кажу Реацт документи:

Примарни случај употребе isMounted()је да се избегне позивање setState()након демонтаже компоненте, јер ће позивање setState()након демонтаже компоненте емитовати упозорење. „Упозорење сетСтате“ постоји да би вам помогло да ухватите грешке, јер је позивање setState()демонтиране компоненте показатељ да апликација / компонента некако није успела да се правилно очисти. Конкретно, позивање setState()демонтиране компоненте значи да ваша апликација и даље држи референцу на компоненту након што је компонента демонтирана - што често указује на цурење меморије! Опширније …

То значи да иако смо избегли непотребан сетСтате, меморија се још увек није очистила. Још увек се дешава асинхрона радња која не зна да је животни циклус компоненте завршен и да више није потребан.

Разговарајмо о правом путу

Овде су спасити дан АбортЦонтроллерс . Према МДН документацији, наводи се:

AbortControllerИнтерфејс представља објекат контролер који омогућава да прекинете једну или више ДОМ захтеве као и када је потребно. Опширније ..

Погледајмо мало дубље овде. Кодом, наравно, јер сви ❤ коду.

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.

Ако желите да прочитате још опширнији пример о томе AbortController, кул људи из МДН-а имају овај заиста леп и елегантан пример на свом Гитхуб-у. Можете погледати овде.

Желео сам да разговарам о тим захтевима за прекид јер их нема много људи. Захтев за прекид преузимања започео је 2015. Ево оригиналног издања ГитХуб о прекиду - коначно је добио подршку око октобра 2017. То је јаз од две године. Вау! Постоји неколико библиотека попут аксиоса које пружају подршку за АбортЦонтроллер. Разговараћу о томе како можете да га користите са аксиом, али прво сам желео да покажем детаљну верзију начина рада АбортЦонтроллер испод хаубе.

Поништавање КСХР захтева у Акиос-у

„Да ли или не. Нема покушаја “. - Иода

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.

Хвала вам што сте одвојили време за читање. Узвикујте мог веома талентованог колегу Кинана што ми је помогао да прочитам овај чланак. Захваљујемо Кенту Ц Доддс-у на томе што је био инспирација у ЈаваСцрипт ОСС заједници.

Поново бих волео да чујем ваше повратне информације о томе. Увек ме можете контактирати на Твиттер-у .

Такође постоји још једно невероватно читање о Аборт Цонтроллер-у које сам пронашао кроз МДН документацију Јаке Арцхибалд . Предлажем да га прочитате, ако имате радозналост попут моје.