Как правильно работать с React, чтобы избежать некоторых распространенных подводных камней

1656570381 kak pravilno rabotat s react chtoby izbezhat nekotoryh rasprostranennyh podvodnyh
изображение-251
Фото Александра Синна / Unsplash

Одна вещь, которую я довольно часто слышу: «Перейдем к Redux” в нашем новом приложении React. Это помогает вам масштабироваться, а данные программы не должны находиться в локальном состоянии React, поскольку это неэффективно. Или когда вы вызываете API и пока обещание ожидает, компонент будет размонтирован, и вы получите такую ​​красивую ошибку.

Невозможно вызвать setState (или forceUpdate) на неподключенном компоненте. Это запрещено, но это указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в componentWillUnmount.

Итак, решение, к которому обычно приходят люди, используется Redux. Я люблю Redux и работу над этим Дан Абрамов делает это просто невероятно! Этот чувак поражает – я хотел бы, чтобы я был наполовину талантлив, как он.

Но я уверен, что, когда Дэн создал Redux, он просто дал нам инструмент в нашем поясе инструментов как помощник. Это не самый лучший инструмент. Вы не используете молоток, если вы можете вкрутить болт с помощью отвертки.

Дэн даже соглашается.

Я люблю React и работаю над ним уже почти два года. Пока не жалею. Лучшее решение. Мне нравится Vue и все классные библиотеки/фреймворки там. Но React занимает особое место в моем сердце. Это помогает мне сосредоточиться на работе, которую я должен выполнять, а не тратить все время на манипуляции с DOM. И делает это самым лучшим и эффективным способом. с его эффективным согласованием.

Я многому научился за последние несколько лет, и я заметил распространенную проблему как среди новых, так и опытных разработчиков React: неправильное использование React при выполнении подписных или асинхронных задач. Мне кажется, что имеющаяся там документация по этому делу недостаточно подготовлена, и поэтому я решил написать эту статью.

Сначала я расскажу о подписках, а затем перейдем к обработке асинхронной отмены задач во избежание утечки памяти в React (основная цель этой статьи). Если этого не сделать, это замедлит работу нашей программы.

1*pHRQWgW6YXlirkX3BTXKeQ
Давайте очистим все подписки/асинхронные задания, и напоминаем, не идите в направлении Мордора

Теперь давайте вернемся к тому красивому сообщению об ошибке, о котором мы сначала говорили:

Невозможно вызвать setState (или forceUpdate) на неподключенном компоненте. Это запрещено, но это указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в componentWillUnmount.

Моя цель этой статьи – убедиться, что никому никогда не придется столкнуться с этой ошибкой и не знать, что с ней делать.

Что мы рассмотрим

  • Очистите подписки, такие как setTimeout/setInterval
  • Очистите асинхронные действия, когда вы вызываете запрос XHR с помощью fetch или библиотеки, как axios
  • Альтернативные методы, одни уверены, другие отрицаются.

Прежде чем я начну, большое оклик Кент Си Доддс, самый крутой человек в Интернете сейчас. Спасибо за то, что нашли время и вернули обществу. Его Youtube подкасты и яйцеголовый курс на Расширенные шаблоны компонентов React удивительные. Просмотрите эти ресурсы, если вы хотите сделать следующий шаг в своих навыках React.

Я спросил у Кента, как лучше избегать setState при демонтировании компонента, чтобы я мог лучше оптимизировать производительность React. Он сделал все больше и сделал об этом видео. Если вы любитель видео, посмотрите это ниже. Это даст вам пошаговый шаг с подробным объяснением.

Итак, давайте приступим к работе.

1: Очистить подписки

Начнем с примера:

Давайте поговорим, что здесь только что произошло. Я хочу, чтобы вы сосредоточились на том counter.js файл, в основном увеличивающий счетчик через 3 секунды.

Это дает ошибку через 5 секунд, потому что я отключил подписку, не очистив ее. Если вы хотите увидеть ошибку снова, просто нажмите кнопку обновления в редакторе CodeSandbox, чтобы увидеть ошибку в консоли.

У меня есть мой файл контейнера index.js просто переключающий компонент счетчика после первых пяти секунд.

Поэтому

— — — →Index.js

— — — — → Counter.js

В моем Index.js я вызываю Counter.js и просто делаю это во время визуализации:

{showCounter ? <Counter /> : null}

The showCounter является логическим значением состояния, устанавливаемого на false после первых 5 секунд, как только компонент монтируется (componentDidMount).

Настоящая вещь, иллюстрирующая нашу проблему здесь, это counter.js файл, увеличивающий количество каждые 3 секунды. Итак, после первых 3 секунд счетчик обновляется. Но как только дело доходит до второго обновления, которое происходит на 6-м во-вторых, index.js файл уже размонтировал компонент счетчика на 5-м второй. К тому моменту, когда компонент счетчика достигнет 6-го во-вторых, он обновляет счетчик вторично.

Он обновляет свое состояние, но проблема. Нет DOM для компонента счетчика, чтобы обновить состояние, и тогда React выдает ошибку. Эта замечательная ошибка, о которой мы говорили выше:

Невозможно вызвать setState (или forceUpdate) на неподключенном компоненте. Это запрещено, но это указывает на утечку памяти в вашем приложении. Чтобы исправить, отмените все подписки и асинхронные задачи в componentWillUnmount.

Теперь, если вы новичок в React, вы можете сказать: Адель… да, но не просто ли мы отключили компонент Counter на 5-й секунде? Если нет компонента для счетчика, как его состояние все еще обновляется на шестой секунде?»

Да, вы правы. Но когда мы делаем нечто подобное setTimeout или setInterval в наших компонентах React он не зависит от нашего класса React и не связан с ним, как вы думаете. Он будет продолжать работать после указанного состояния, пока вы не отмените его подписку.

Теперь вы, возможно, уже делаете это, когда ваше условие будет выполнено. Но что делать, если ваше условие еще не выполнено, и пользователь решил сменить страницы, где это действие все еще выполняется?

Лучший способ очистить подобные подписки – это ваш componentWillUnmount жизненный цикл Вот пример как это можно сделать. Проверьте метод componentWillUnmount файла counter.js:

И это почти все для setTimout & setInterval.

2: API (XHR) перерыв

  • Старый уродливый подход (устаревший)
  • Новый хороший подход (основная цель этой статьи)

Итак, мы обсудили подписку. Но что делать, если вы делаете асинхронный запрос? Как это отменить?

Старый способ

Прежде чем говорить об этом, я хочу поговорить об устаревшем методе в React, который называется isMounted()

До декабря 2015 существовал метод под названием isMounted в React. Подробнее об этом можно прочитать в React блог. То, что он сделал, было примерно таким:

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('
    if (this._isMounted) {
      this.setState({ user: response.data })
    }
  }
  componentDidMount() {
    this._isMounted = true
  }
  componentWillUnmount() {
    this._isMounted = false
  }
  render() {
    return (
      <div>
        <button onClick={this.handleButtonClick}>Click Me</button>
        <pre>{JSON.stringify(this.state.user, null, 2)}</pre>
      </div>
    )
  }
}

Для целей этого примера я использую библиотеку под названием axios для подачи запроса XHR.

Давайте пройдемся через это. Я сначала поставил this_isMounted к false прямо рядом с тем, где я инициализировал свое состояние. Как только жизненный цикл componentDidMount получает вызов, я устанавливаю this._isMounted к истине. В это время, если конечный пользователь нажимает кнопку, делается запрос XHR. я использую randomuser.me. Как только обещание будет решено, я проверяю, компонент ли все еще смонтирован this_isMounted. Если это правда, я обновляю свое состояние, иначе я игнорирую его.

Пользователь может нажать кнопку при разрешении асинхронного вызова. Это приведет к тому, что пользователь переключается по страницам. Чтобы избежать ненужного обновления состояния, мы можем просто обработать это с помощью нашего метода жизненного цикла. componentWillUnmount. Я просто поставил this._isMounted к фальшивому. Поэтому каждый раз, когда асинхронный вызов API будет решен, он проверит, this_isMounted false, и тогда он не будет обновлять состояние.

Этот подход действительно выполняет работу, но, как говорят документы React:

Основной вариант использования для isMounted() это избегать звонков setState() после того, как компонент был размонтирован, поскольку вызов setState() после размонтирования компонента будет выдаваться предупреждение. Предупреждение setState существует, чтобы помочь вам обнаружить ошибки, потому что вызов setState() на неподключенном компоненте является признаком того, что ваше приложение/компонент каким-либо образом не удалось очистить должным образом. Вернее, звонит setState() в неотмонтированном компоненте означает, что ваше приложение все еще содержит ссылку на компонент после того, как компонент был отмонтирован, что часто свидетельствует об утечке памяти! Подробнее…

Это означает, что, хотя мы ушли от ненужного setState, память все еще не очистилась. Все еще происходит асинхронное действие, не знающее, что жизненный цикл компонента закончился, и оно больше не нужно.

Давайте поговорим о правильном пути

Здесь, чтобы спасти день AbortControllers. Согласно документации MDN указано:

The AbortController Интерфейс представляет собой объект контроллера, позволяющий прерывать один или несколько запросов DOM по желанию. Детальнее ..

1*CLnYV7AQDdgpS-LAQ6fLlg

Давайте рассмотрим здесь немного поглубже. С кодом, конечно, потому что все ❤ кодируют.

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

Сначала создаем a новый AbortController и назначить его переменной с названием myController. Затем делаем а сигнал для этого AbortController. Думайте о сигнале как об индикаторе, который сообщает нашим запросам XHR, когда пора отменить запрос.

Предположим, что у нас есть 2 кнопки, Download и Abort . Кнопка загрузки загружает видео, но что делать, если мы хотим отменить запрос на загрузку во время загрузки? Нам просто нужно позвонить myController.abort(). Теперь этот контролер отменит все связанные с ним запросы.

Как можете спросить?

После того, как мы сделали var myController = new AbortController() мы сделали это var mySignal = myController.signal . Теперь в моем запросе на получение, где я сообщаю ему URL-адрес и полезную нагрузку, мне просто нужно передать mySignal чтобы связать/сообщить об этом FETCh запрос с моим удивительным AbortController.

Если вы хотите прочитать еще более расширенный пример о AbortControllerкрутые люди в MDN имеют этот действительно хороший и изящный пример на своем Github. Вы можете проверить это здесь.

Я хотел поговорить об этих запросах на прерывание потому, что мало кто о них знает. Запрос на отмену в избирателе начался в 2015 году. Вот оригинальная проблема GitHub по Abort — она наконец получила поддержку примерно в октябре 2017 года. Это два года. Вот Да! Есть несколько библиотек, как axios которые оказывают поддержку AbortController. Я обсудю, как вы можете использовать его с axios, но сначала я хотел показать подробную подкапотную версию того, как работает AbortController.

Отмена запроса XHR в Axios

«Делайте или не делайте. Нет никакой попытки». — Йода

Реализация, о которой я говорил выше, не касается React, но это то, что мы обсудим здесь. Основная цель этой статьи – показать вам, как очистить ненужные манипуляции DOM в React, когда сделан запрос XHR, а компонент размонтирован, пока запрос находится в состоянии ожидания. Вау!

Итак, без лишних разговоров, вот мы идем.

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(' {
        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 (
        <div>
          <pre>{JSON.stringify(this.state.user, null, 2)}</pre>
        </div>
      )
    }
 
}

Давайте пройдемся по этому коду

я установил this.signal к axios.CancelToken.source()который в основном создает экземпляр нового AbortController и назначает сигнал об этом AbortController к this.signal. Далее я вызываю метод componentDidMount звонил this.onLoadUser() который вызывает случайную информацию пользователя из стороннего API randomuser.me. Когда я вызываю этот API, я также передаю сигнал свойства в axios cancelToken

Следующее, что я делаю, это в моем componentWillUnmount где я вызываю метод abort, который связан с этим signal. Теперь предположим, что как только компонент был загружен, API было вызвано и XHR request went in a pending state.

Теперь запрос был на рассмотрении (т.е. он не был решен или отклонен, но пользователь решил перейти на другую страницу. Как только метод жизненного цикла componentWillUnmount будет вызвано, мы отменим наш запрос API. Как только API будет прерван/отменен, обещание будет отклонено, и оно попадет в catch блок этого try/catch заявление, в частности в if (axios.isCancel(err) {} блокировать.

Теперь мы четко знаем, что API была отменена, потому что компонент был размонтирован и поэтому регистрирует ошибку. Но мы знаем, что нам больше не нужно обновлять это состояние, поскольку оно больше не нужно.

PS: Вы можете использовать тот же сигнал и передавать ему сколько угодно запросов XHR в вашем компоненте. Когда компонент будет демонтирован, все запросы XHR, находящиеся в состоянии ожидания, будут отменены при вызове componentWillUnmount.

Последние детали

Поздравляю! 🙂 Если вы дочитали до этого момента, вы только что узнали, как отменить запрос XHR на ваших собственных условиях.

Давайте продолжим еще чуть-чуть. Обычно ваши запросы XHR находятся в одном файле, а ваш основной компонент контейнера – в другом (из которого вы вызываете этот метод API). Как передать этот сигнал в другой файл и все равно отменить запрос XHR?

Вот как вы это делаете:

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 (
        <div>
          <pre>{JSON.stringify(this.state.user, null, 2)}</pre>
        </div>
      )
    }
  };
 
}
export const onLoadUser = async myCancelToken => {
  try {
    const { data } = await axios.get(' {
      cancelToken: myCancelToken,
    })
    return data;
  } catch (error) {
    throw error;
  }
};

Я надеюсь, что это помогло вам, и я надеюсь, что вы чему-то научились. Если вам понравилось, пожалуйста дайте ему несколько хлопков.

Спасибо, что нашли время для чтения. Крикните моему очень талантливому коллеге Кинан за помощь в подтверждении, прочтите эту статью. Благодаря Кент Си Доддс является источником вдохновения в сообществе JavaScript OSS.

Опять же, я хотел бы услышать ваш отзыв об этом. Вы всегда можете связаться со мной Twitter.

Также есть еще одно удивительное чтение Контроллер прерывания что я нашел через MDN документация за Джейк Арчибальд. Предлагаю вам прочесть, если у вас такая любознательная натура, как моя.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *