Каков правильный шаблон в react js для вызова метода компонента при изменении определенных свойств?

Используя React и Redux, представьте, что у вас есть компонентный метод, который отправляет запрос внешнему API.

import React, { Component } from 'react';
import { connect } from 'react-redux';

class MyComp extends Component {

  boolUpdate (val) {
    fetch('http://myapi.com/bool', { val });
  }

  shouldComponentUpdate (nextProps) {
    return false;
  }

  render () {
    return <h1>Hello</h1>;
  }

}

const mapStateToProps = ({ bool }) => ({ bool });

export default connect(mapStateToProps)(MyComp);

Теперь предположим, что вы хотите вызывать boolUpdate() каждый раз, когда свойство bool изменяется, но это не должно вызывать обновление компонента, потому что ничего в рендеринге компонента не затрагивается.

Как лучше всего это сделать в React?

До недавнего времени люди делали что-то вроде:

componentWillReceiveProps (nextProps) {
  if (nextProps.bool !== this.props.bool) this.boolUpdate(nextProps.bool);
}

Но с React v16.3 componentWillReceiveProps() устарел. В этом примере мы также не можем использовать componentDidUpdate(), потому что shouldComponentUpdate() предотвращает это. getDerivedStateFromProps() - это статический метод, поэтому у него нет доступа к методам экземпляра.

Итак, единственный вариант, который у нас остался, похоже, использует сам shouldComponentUpdate(). Что-то вроде:

shouldComponentUpdate (nextProps) {
  if (nextProps.bool !== this.props.bool) this.boolUpdate(nextProps.bool);
  return false;
}

Мне это кажется довольно "хакерским", и не то, для чего был разработан shouldComponentUpdate().

Кто-нибудь может предложить лучший образец?

Есть ли предпочтительный способ прислушиваться к конкретным изменениям свойств и запускать методы компонентов?

Спасибо!

Не по теме. Ситуация, когда у вас есть какой-то логический флаг в хранилище redux, но вызов внешнего API из метода компонента очень подозрительна. :)

Yury Tarabanko 11.04.2018 12:38

Что, если бы вы справились с этим вне компонента и вместо этого в редукторе. Каждый раз, когда вы обновляете значение bool в состоянии, вы также делаете вызов API?

Christopher Moore 11.04.2018 12:38

@ChristopherMoore, это действительно можно было бы обработать в промежуточном программном обеспечении Redux. Мне просто интересно, есть ли хороший шаблон для обработки этого непосредственно в компоненте.

Pensierinmusica 11.04.2018 13:25

Есть ли конкретная причина, по которой вы хотите, чтобы это обрабатывалось в компоненте? Как упоминает @YuryTarabanko выше, это запах кода

Christopher Moore 11.04.2018 13:33

Новый шаблон ожидания ответа будет выполнять асинхронный вызов внутри рендера :). Итак, понятия не имею, какое место лучше. Один из способов сделать это - сохранить старое значение в свойстве экземпляра и сравнить с ним в componentDidUpdate.

Mukesh Soni 11.04.2018 13:36

@ChristopherMoore У меня такое чувство, что перенос логики компонента в промежуточное ПО только потому, что он асинхронный, иногда усложняет в сложном приложении отслеживание того, где что-то происходит. Мне бы хотелось, чтобы было элегантное решение, позволяющее сохранить эту логику внутри самого компонента.

Pensierinmusica 11.04.2018 13:36

@MukeshSoni: да, это шаблон, к которому React после v16.3 склоняется, хотя он не работает, если вы избегаете повторного рендеринга, возвращая false в shouldComponentUpdate(), потому что тогда componentDidUpdate() никогда не вызывается. И в этом случае, например, повторный рендеринг не нужен, поэтому было бы лучше его избежать.

Pensierinmusica 11.04.2018 13:39

@Pensierinmusica по моему опыту, разделение проблем действительно помогает в сложном приложении. Промежуточное ПО redux содержит все ваши действия и логику, а компоненты просто концентрируются на построении DOM.

Christopher Moore 11.04.2018 13:41

@ChristopherMoore возможно, хотя в моем случае асинхронная логика будет зависеть от того, где пользователь находится в пользовательском интерфейсе в этот момент. Итак, не совсем уверен. Я мог бы отслеживать текущий маршрут и использовать условные выражения, но кажется более элегантным оставить его в самом компоненте. Опять же, не уверен. Также интересными вариантами кажутся RxJS и Redux Observable: github.com/redux-observable/redux-observable

Pensierinmusica 11.04.2018 13:50

@Pensierinmusica Я думаю, что ваше решение с shouldComponentUpdate на самом деле наименее хакерское и ваше самое чистое и логичное место для этого. Следом за тем, чтобы сделать это там, где, очевидно, возникла измененная опора, я предполагаю, что это будет действие. Так почему бы не позволить этому действию выполнить запрос? Ваш поворот с ограничением «в компоненте». Это кажется хакерским, потому что это очень необычный сценарий, выходящий за рамки библиотеки визуализации представлений. Реквизиты меняются с помощью redux, а вызов api выполняется с чем-то, что, как я полагаю, не реагирует.

timotgl 11.04.2018 19:07
10
10
1 080
2

Ответы 2

Основываясь на обсуждении React документы и это на github, местом для получения новых данных на основе изменения свойств на самом деле является componentDidUpdate.

Рендеринг фактически был разделен на две фазы: Render Phase, который является чистым и не создает побочных эффектов, и Commit Phase, который может запускать побочные эффекты, работать с DOM и планировать обновления.

Вы можете увидеть, что это хорошо объяснено на диаграмме Дэна Абрамова: enter image description here

Дэн также упомянул:

People used to mix these two different things in componentWillReceiveProps, which is why we have to split it into a pure method (getDerivedStateFromProps) and an existing impure one where it’s okay to do side effects (componentDidUpdate).

А для самого решения я прилагаю пример из документации:

Fetching external data when props change

Here is an example of a component that fetches external data based on props values:

Before:

componentDidMount() {
  this._loadAsyncData(this.props.id);
}

componentWillReceiveProps(nextProps) {
  if (nextProps.id !== this.props.id) {
    this.setState({externalData: null});
    this._loadAsyncData(nextProps.id);
  }
}

After:

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.id !== prevState.prevId) {
      return {
        externalData: null,
        prevId: nextProps.id,
      };
    }
    return null;
  }

  componentDidMount() {
    this._loadAsyncData(this.props.id);
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.state.externalData === null) {
      this._loadAsyncData(this.props.id);
    }
  }

Этот ответ должен быть более исчерпывающим, но я думаю, что в этом конкретном случае есть более простое решение :-) stackoverflow.com/a/49779063/458193

Dan Abramov 11.04.2018 17:32

@DanAbramov, я совершенно пропустил эту часть, слишком рано попал в нее. Спасибо.

Matan Bobi 11.04.2018 17:40

Я взял на себя смелость отредактировать ваш ответ, чтобы он более четко соответствовал документации - например, вы добавили некоторый код в «после», который уже присутствовал в состоянии «до», что немного сбивает с толку. Надеюсь, что все в порядке, и спасибо за ответ!

Dan Abramov 11.04.2018 17:45

Если вы хотите запустить какой-то код (например, выборку данных) при изменении свойств, сделайте это в componentDidUpdate.

componentDidUpdate(prevProps) {
  if (prevProps.id !== this.props.id) {
    this.fetchData();
  }
}

В вашем примере это не сработает, потому что shouldComponentUpdate возвращает false. Я бы сказал, что это не очень распространенный случай, потому что обычно вы все равно хотите выполнить повторный рендеринг, если свойства меняются.

Например, если идентификатор пользователя изменится, вы можете захотеть показать индикатор загрузки во время загрузки данных для нового пользователя.. Поэтому в данном случае не очень полезно избегать повторного рендеринга.

Однако, если вы абсолютно уверены, что оба необходимо предотвратить повторную визуализацию а также необходимо выполнить побочный эффект, такой как выборка данных при изменении реквизита, вы можете разделите свой компонент на две части. Внешний компонент будет выполнять выборку данных в componentDidUpdate и возвращать <InnerComponent {...this.props} />. Внутренний компонент будет иметь реализацию shouldComponentUpdate, которая предотвращает дальнейший повторный рендеринг. Опять же, я бы не ожидал, что это будет обычным сценарием, но вы можете это сделать.

Другие вопросы по теме