Redux: Компоненты, которые не перерисовываются с помощью connect ()

Созданный на 20 авг. 2015  ·  32Комментарии  ·  Источник: reduxjs/redux

Я знакомлюсь с Redux, прочитав документацию, и начинаю просто изменять реальный пример. В моем случае я заменил вызовы HTTP API GitHub своими собственными и заменил концепции Repo и User аналогичными в моей среде. Пока что не должно сильно отличаться, правда?

Вот где у меня проблемы:

Мои вызовы API выполняются успешно, ответ JSON получает camelCased и нормализованный, _ но_ мой компонент не перерисовывается после успешного выполнения запроса API. Вскоре после отправки USER_REQUEST и обновления компонента User для отображения «Загрузка ...» отправляется USER_SUCCESS , и его следующее состояние действительно содержит заполненный ключ entities :

pasted_image_8_19_15__3_27_pm

Это будет означать, что проблема в промежуточном программном обеспечении, верно? Я должен хранить объекты из действия в магазине, а компонент должен наблюдать за изменением и повторным рендерингом, да?

Где, в реальном примере, успешно извлеченные и нормализованные данные _ фактически_ помещаются в хранилище? Я вижу, где это нормализуется, но не где результат этой нормализации доставляется в магазин.

Спасибо!

question

Самый полезный комментарий

Данные устанавливаются / обновляются / удаляются в магазине в результате обработки действий в редукторах. Редукторы получают текущее состояние части вашего приложения и ожидают возврата нового состояния. Одна из наиболее распространенных причин, по которой ваши компоненты могут не выполнять повторный рендеринг, заключается в том, что вы изменяете существующее состояние в редукторе вместо того, чтобы возвращать новую копию состояния с необходимыми изменениями (см. Раздел «Устранение неполадок» ). Когда вы изменяете существующее состояние напрямую, Redux не обнаруживает разницы в состоянии и не уведомляет ваши компоненты об изменении хранилища.

Поэтому я бы обязательно проверил ваши редукторы и убедился, что вы не мутируете существующее состояние. Надеюсь, это поможет!

Все 32 Комментарий

Данные устанавливаются / обновляются / удаляются в магазине в результате обработки действий в редукторах. Редукторы получают текущее состояние части вашего приложения и ожидают возврата нового состояния. Одна из наиболее распространенных причин, по которой ваши компоненты могут не выполнять повторный рендеринг, заключается в том, что вы изменяете существующее состояние в редукторе вместо того, чтобы возвращать новую копию состояния с необходимыми изменениями (см. Раздел «Устранение неполадок» ). Когда вы изменяете существующее состояние напрямую, Redux не обнаруживает разницы в состоянии и не уведомляет ваши компоненты об изменении хранилища.

Поэтому я бы обязательно проверил ваши редукторы и убедился, что вы не мутируете существующее состояние. Надеюсь, это поможет!

В дополнение к тому, что сказал @ernieturner , убедитесь, что функция mapStateToProps в вашей функции connect(mapStateToProps)(YourConnectedComponent) отображает соответствующее состояние (в данном случае entities ), чтобы подключенный компонент слушал в этот кусок государства.

Также имейте в виду, что connect() делает поверхностное сравнение: https://github.com/rackt/react-redux/blob/d5bf492ee35ad1be8ffd5fa6be689cd74df3b41e/src/components/createConnect.js#L91

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

Ах, понял! Мой mapStateToProps пытался извлечь объект из состояния, но не указывал правильный ключ. Вместо извлечения entities.users[login] я установил для него значение entities.users[id] . Объект entities.users был привязан к строке login , поэтому, когда числовое значение id не было найдено, никакие реквизиты фактически не менялись, поэтому повторная визуализация не требовалась.

Спасибо всем за помощь! Я очень ценю время, которое вы потратили на помощь.

@timdorr
Это просто смешно. Как заставить connect() проверять глубокие изменения? В противном случае придется создавать десятки компонентов-контейнеров для каждого более глубокого уровня состояния. Это не нормально

У вас обязательно должно быть много подключенных компонентов в любом приложении разумного размера. Если у вас есть один связанный компонент верхнего уровня, вы будете без необходимости повторно визуализировать большие части вашего приложения каждый раз, когда состояние вообще изменяется. И вы избежите одной из основных оптимизаций react-redux, которая заключается в повторном рендеринге только тогда, когда состояние, на которое оно подписано, изменяет.

Если вам нужно оценить сложный запрос состояния, используйте повторный выбор, чтобы ускорить это.

@timdorr @wzup

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

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

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

@naw Спасибо за совет. Исходя из этого, я добавил новый редуктор, в котором у меня возникла эта проблема:

const reducers = combineReducers({
    //...bunch of reducers which always return objects of 3 or 4 properties that change sometimes but
    // the shape stays the same
    dataVersion: (state = Symbol(), action = {}) => {
        switch (action.type) {
            case SAME_ACTION_AS_THE_ONE_THAT_UPDATES_A_COMPLEX_PROPERTY:
            case ANOTHER_ACTION_THAT_ALSO_UPDATES_A_COMPLEX_PROPERTY:
                return Symbol():
            default:
                return state;
        }
    }
})

Хотя мне это все еще кажется странным. Если у меня есть редуктор:

    switch (action.type) {
       case UPDATE_THIS:
           return {a: action.a, b: action.b, c: action.c};
       default:
           return state;
     }

тот факт, что я возвращаю вновь инициализированный объект, который имеет ту же форму, что и предыдущее состояние, должен быть достаточным для запуска previousState !== newState . Если я должен каждый раз возвращать неизменяемые данные, мне кажется, что более неэффективно проводить поверхностное сравнение формы, а не просто идентичности. (Если предположить, что Redux на самом деле делает это. Тот факт, что мой обходной путь Symbol работает, заставляет меня думать, что это то, что происходит)

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

@Zacqary Сравнение текущего состояния с предыдущим выполняется со строгим равенством (===). Сравнение stateProps (результат mapStateToProps) выполняется с мелким равенством.

@Zacqary

mapStateToProps «собирает» данные для вашего подключенного компонента, заходя в магазин.

mapStateToProps извлекает различные части из хранилища и назначает их ключам в новом объекте stateProps .

Результирующий объект stateProps будет каждый раз новым (поэтому его идентичность меняется), поэтому мы не можем просто сравнить идентичность объекта самого объекта stateProps --- мы _ должны_ спуститься на один level и проверьте идентичность объекта каждого значения, и здесь, как сказал @jimbolla, в игру вступает shallowEqual .

Обычно проблемы возникают, когда вы пишете редукторы таким образом, что идентификатор объекта части состояния _не изменяется_, в то время как некоторые вложенные атрибуты внутри него _ меняются. Это изменяющееся состояние, а не неизменяемое возвращение нового состояния, что заставляет react-redux думать, что ничего не изменилось, хотя на самом деле что-то _did_ изменилось.

того факта, что я возвращаю вновь инициализированный объект, который имеет ту же форму, что и предыдущее состояние, должно быть достаточно для запуска previousState! == newState.

Если вы вернете новый объект для некоторого фрагмента состояния, а mapStateToProps использует этот фрагмент состояния в качестве значения для одного из своих ключей, тогда react-redux увидит, что идентификатор объекта изменился, и не помешает повторный рендер.

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

Стоит подумать, нужно ли просто переместить дополнительный компонент в компонент connected() чтобы унаследовать свойства. Я дошел до этой проблемы, попытавшись connect() двух компонентов, но это было ненужной сложностью.

@ernieturner Я знаю, что это раздел устранения неполадок .

Ура

У меня была эта проблема, ну не совсем так.
Я применил свойства к состоянию, например:

  constructor(props){
    this.state = {
      text: props.text
    }
  }

И это не сработало. Поэтому я применил значение непосредственно из реквизита, например

{this.props.text}

И работает отлично :)

@AlexanderKozhevin Если я не ошибаюсь, в вашем конструкторе отсутствует super(props) .
Обычно вам даже не разрешается использовать this перед вызовом super(props) .

@ cdubois-mh Правильно, спасибо за внимание.

Я написал собственный корневой редуктор приложения и решил эту проблему.
Возврат копии всего состояния
{...state}
в конце мне помог root reducer

@daedalius Вот как должен работать редуктор.
Он должен вернуть состояние, если действие не имеет значения, или вернуть копию состояния с ожидаемыми измененными значениями.

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

Проверьте это состояние:

{
    "clients": [
        { "name": "John", "cid": 4578 },
        { "name": "Alex", "cid": 5492 },
        { "name": "Bob",  "cid": 254 }
    ]
}

Если вы изменили имя клиента, но не вернули клон состояния, значит, вы не изменили состояние, вы изменили объект в массиве.

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

Если я ошибаюсь, пожалуйста, поправьте меня, но я так понимаю работу редукторов.

Ага, это правильно. Также, @daedalius , обратите внимание, что вы не хотите _всегда_ делать неглубокую копию state - вы должны делать это только в том случае, если что-то изменилось, иначе вы можете столкнуться с ненужным повторным рендерингом ваших компонентов.

принудительное обновление

<AfterConnect
    _forceUpdate={Symbol()}
/>

@BrookShuihuaLee Что это должно значить? вы не предоставили никакой информации или объяснения вашего фрагмента кода. Что оно делает? Почему это актуально для текущего обсуждения?

@CedSharp Функция shallowEqual вернет false, если мы установим _forceUpdate = {Symbol ()}

@BrookShuihuaLee Было бы здорово, если бы вы могли включить подобное объяснение в свой первый ответ, так люди поймут его лучше ^^

@CedSharp да

@BrookShuihuaLee : Это кажется плохой идеей. Почему вы хотите это сделать?

@BrookShuihuaLee , я согласен с @markerikson .
Есть причина, по которой вы НЕ должны возвращать клон, если состояние не изменилось. Если вы не можете предоставить действительный пример, в котором ваше решение кажется необходимым, я настоятельно рекомендую не разбираться в том, как работает Redux.

@CedSharp
@markerikson
Потому что компонент AfterConnect будет часто повторно отрисовываться по замыслу. Можно повторно отрендерить его еще раз, когда его родительский компонент перерисовывается. Я не хочу выполнять все вычисления в родительском компоненте и устанавливать тонны свойств для AfterConnect.

@BrookShuihuaLee , извините, но какое отношение ваш компонент имеет к обсуждаемому вопросу?
Если я правильно понял, вы говорите о собственном кастомном компоненте? ForceUpdate - это то, что очень сильно не приветствуется в мире React, и я бы не рекомендовал его в качестве решения.

Вместо этого вы могли бы использовать наблюдаемое решение, когда вы ищете изменения где-то еще, а не в состоянии? Я не знаю, зачем вам нужно повторно отображать этот конкретный компонент без изменения его состояния, но если вы знаете, почему он должен повторно отображаться, может быть, вы могли бы использовать что-то вроде mobx?
(https://github.com/mobxjs/mobx)

Я пытаюсь понять, каково ваше решение и почему оно применимо в этой ситуации.

@CedSharp Я не имею в виду, что все должны использовать forceUpdate. Но рекомендация - это одно, выбор - другое. React также сохраняет ReactComponent.prototype.forceUpdate для разработчиков. Людям нужен выбор.

Я пытался дать другой выбор. Может быть, это не лучший выбор.

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

case SOME_ACTION:
      return {
        ...state,
        ts: (new Date).getTime(),
      };

IMO, не очень хорошее решение, но для всех, кто в настоящее время привязан (т.е. вам нужно выпустить в конце дня), это быстрое исправление, и вам не нужно форсировать рендеринг где-то еще.

Код следует отредактировать так, чтобы неглубокое сравнение даже в глубоко вложенном состоянии работало.

Состояние должно быть неизменным. Глубоко скопируйте состояние в reducer, тогда неглубокого сравнения в connect будет достаточно и эффективно.

Моя проблема заключалась в наличии объекта с массивами внутри него. Поскольку это было поверхностное сравнение, оно никогда не доходило до объектов в дочерних массивах. Я решил это, используя list:state.obj.list вместо lists:state.obj в mapStateToProps 👍🏼

Я только что столкнулся с этой проблемой, и, прочитав первые пару ответов, я проверил свой редуктор и заметил, что у меня там есть опечатка. Я переименовал свойство и обновил INITIAL_STATE, но забыл изменить ключ, который обновлялся этим проблемным действием.

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

ПРОВЕРЬТЕ ТИПО В ВАШЕМ РЕДУКТОРЕ! :)

Была ли эта страница полезной?
0 / 5 - 0 рейтинги