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


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-компонентное изменение состояния}))

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.