Как компонент, связанный с редукцией, знает, когда нужно выполнить повторный рендеринг?

87

Я, вероятно, упускаю что-то очень очевидное и хотел бы очистить себя.

Вот мое понимание.
В наивном компоненте реакции у нас есть states& props. Обновление stateс setStateповторным оказывает весь компонент. propsв основном предназначены только для чтения, и их обновление не имеет смысла.

В компоненте реакции, который подписывается на хранилище redux через что-то вроде store.subscribe(render), он, очевидно, повторно отображает каждый раз, когда хранилище обновляется.

В response-redux есть помощник, connect()который вводит часть дерева состояний (которая представляет интерес для компонента) и actionCreators в propsотношении компонента, обычно через что-то вроде

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Но с пониманием того, что a setStateнеобходим для TodoListComponentреакции на изменение дерева состояний redux (повторный рендеринг), я не могу найти какой-либо stateили setStateсвязанный код в TodoListфайле компонента. Читается это примерно так:

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

Может ли кто-нибудь указать мне в правильном направлении, что мне не хватает?

PS Я следую примеру списка задач, поставляемого с пакетом redux .

Джо Льюис
источник

Ответы:

111

connectФункция генерирует компонент оболочки, присоединяется к магазину. При отправке действия уведомляется обратный вызов компонента оболочки. Затем он запускает вашу mapStateфункцию и неглубоко сравнивает объект результата с этого момента с объектом результата с последнего раза (поэтому, если вы переписываете поле хранилища redux с тем же значением, оно не запускает повторную визуализацию). Если результаты отличаются, то он передает результаты вашему «настоящему» компоненту в качестве свойств.

Дэн Абрамов написал отличную упрощенную версию connectat ( connect.js ), которая иллюстрирует основную идею, но не показывает никаких работ по оптимизации. У меня также есть ссылки на ряд статей о производительности Redux , в которых обсуждаются некоторые связанные идеи.

Обновить

React-Redux v6.0.0 внес некоторые серьезные внутренние изменения в то, как подключенные компоненты получают свои данные из хранилища.

В рамках этого я написал сообщение, в котором объясняется, как работает connectAPI и его внутреннее устройство и как они менялись с течением времени:

Идиоматический Redux: история и реализация React-Redux

маркериксон
источник
Благодарность! Вы тот же человек, который помог мне на днях @ HN - news.ycombinator.com/item?id=12307621 с полезными ссылками? Приятно познакомиться :)
Джо Льюис
4
Да, это я :) Я трачу путь слишком много времени , ища для обсуждения Redux онлайн - Reddit, HN, SO, Medium, а также различные другие места. Рад помочь! (Также к вашему сведению, я настоятельно рекомендую каналы чата Reactiflux на Discord. Многие люди тусуются там и отвечают на вопросы. Я обычно онлайн там по вечерам в США. Ссылка для приглашения находится на reactiflux.com .)
markerikson
Предполагая, что это ответ на вопрос, не возражаете ли принять ответ? :)
markerikson 03
Сравниваются ли сами объекты или свойства объектов до и после? Если последнее, проверяется ли только первый уровень, это то, что вы подразумеваете под «мелким»?
Энди
Да, средство «мелкой проверки равенства» , чтобы сравнить первые поля на уровне каждого объекта: prev.a === current.a && prev.b === current.b && ...... Это предполагает, что любые изменения данных приведут к появлению новых ссылок, что означает, что требуются неизменяемые обновления данных вместо прямой мутации.
markerikson
13

Мой ответ немного необычный. Это проливает свет на проблему, которая привела меня к этой публикации. В моем случае казалось, что приложение не подвергалось повторному рендерингу, несмотря на то, что оно получило новые реквизиты.
У разработчиков React был ответ на этот часто задаваемый вопрос: если (хранилище) было изменено, в 99% случаев это причина, по которой реакция не будет повторно отображаться. Но про остальные 1% ничего. Мутации здесь не было.


TL; DR;

componentWillReceivePropsэто то, как stateможно синхронизировать с новым props.

Пограничный случай: После stateобновления, то приложение делает повторно вынести!


Оказывается, если ваше приложение используется только stateдля отображения своих элементов, оно propsможет обновляться, но stateне обновляется , поэтому повторного рендеринга не требуется.

У меня было stateчто зависело от propsполученного от redux store. Нужных мне данных еще не было в магазине, поэтому я взял их componentDidMount, как положено. Я вернул реквизиты, когда мой редуктор обновил хранилище, потому что мой компонент подключен через mapStateToProps. Но страница не отображалась, а состояние все еще было заполнено пустыми строками.

Например, пользователь загрузил страницу редактирования сообщения с сохраненного URL. У вас есть доступ к postIdURL-адресу, но информации storeеще нет, поэтому вы ее получите. Элементы на вашей странице - это контролируемые компоненты, поэтому все данные, которые вы показываете, находятся внутри state.

Используя redux, данные были извлечены, хранилище обновлено и компонент был connectизменен, но приложение не отразило изменения. При ближайшем рассмотрении propsбыли получены, но приложение не обновлялось. stateне обновлялся.

Хорошо, propsбудет обновлять и распространять, но stateне будет. Вам нужно специально сообщить stateоб обновлении.

Вы не можете сделать это в render(), и componentDidMountуже закончили его циклы.

componentWillReceivePropsздесь вы обновляете stateсвойства, которые зависят от измененного propзначения.

Пример использования:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

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

Методы жизненного цикла компонентов ReactJs - глубокое погружение

componentWillReceivePropsздесь вы будете обновлять, stateчтобы синхронизироваться с propsобновлениями.

После stateобновления, а затем поля в зависимости от state дел повторной визуализации!

ШерилХохман
источник
3
С этого React 16.3момента вы должны использовать getDerivedStateFromPropsвместо componentWillReceiveProps. Но на самом деле вы даже не должны делать все это так, как здесь
kyw
он не работает, всякий раз, когда состояние изменяется, componentWillReceiveProps будет работать так часто, как я пытаюсь, это не работает. В частности, многоуровневые вложенные или сложные реквизиты, поэтому мне нужно назначить компоненту с идентификатором и инициировать изменение для него, а также обновить состояние для повторной визуализации новых данных
May Weather VN
0

Этот ответ представляет собой краткое изложение статьи Брайана Вона, озаглавленной « Вам, вероятно, не нужно производное состояние» (7 июня 2018 г.).

Получение состояния из props - это антипаттерн во всех его формах. В том числе с использованием старых componentWillReceivePropsи новых getDerivedStateFromProps.

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

Две лучшие практические рекомендации

Рекомендация 1. Полностью управляемый компонент
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
Рекомендация 2. Полностью неконтролируемый компонент с ключом.
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Две альтернативы, если по какой-либо причине рекомендации не работают в вашей ситуации.

Альтернатива 1: сбросить неконтролируемый компонент с помощью ID prop
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Альтернатива 2: сбросить неуправляемый компонент с помощью метода экземпляра
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}
Позвольте мне подумать об этом
источник
-2

поскольку я знаю, что только redux делает, при изменении состояния хранилища вызывает componentWillRecieveProps, если ваш компонент зависел от мутированного состояния, а затем вы должны заставить свой компонент обновлять его, как это

1-store State change-2-call (componentWillRecieveProps (() => {3-компонентное изменение состояния}))

Газале Явахери
источник