Оптимизация производительности Redux + Immutable.js

внешний интерфейс React.js Immutable.js Redux

(Чтение этой статьи займет около 2 минут)

введение

Как мы все знаем, одной из самых проблемных частей при использовании Redux является написание редьюсера, потому что Redux требует, чтобы состояние было неизменным, то есть измененное дерево состояний должно быть новой ссылкой. Поэтому редюсер часто записывают так:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

Многие назовут это глубоким клонированием, но это не так, этот процесс не является ни глубоким, ни поверхностным клонированием.

Правильное написание редуктор

Во-первых, давайте поговорим о том, возможно ли глубокое клонирование.Если ваш редьюсер выполняет глубокое клонирование каждый раз, когда изменяется состояние, ваше приложение, несомненно, может работать.Time TravellingКонечно, его можно использовать, так в чем проблема?

Давайте посмотрим на схему:

Все дерево состояний перестраивается, что означаетPureComponentа такжеshouldComponentUpdateКомпоненты, которые не реализованы, будут перерисованы.

Итак, в реальном проекте мы представилиImmutable.js, просто чтобы избежать написания громоздких или неправильных редукторов. Похожие такжеimmerтакая библиотека.

Immutable.js использует для внутреннего использованияShared StructureЧтобы избежать глубокого клонирования, с одной стороны, улучшается производительность самого Immutable.js, а с другой — помогает React рендериться более эффективно. так:

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

При изменении узла 4 узлы 1 и 2 не должны быть равны до и после изменения, но узлы 3, 5 и 6 остаются одинаковыми без каких-либо изменений. нам даже не нужноdeepEquals, можно сравнивать ссылки, потому что Immutable.js может гарантировать, что они не изменятся.

Поэтому наши компоненты React автоматически получат наилучшую оптимизацию, если они используют PureComponent, а компоненты, не связанные с изменениями, не будут перерисовываться.

Правильное использование Immutable.js с React

Однако при реальном использовании мы снова столкнулись с проблемами: даже с Immutable.js остается много несвязанных между собой компонентов, которые обновляются при каждом обновлении. Поискав в коде, я обнаружил, что сейчас у нас много чего написано так:

const mapStateToProps = state => {
  const user = selectCurrentUser(state)
  const me = user.toJS()
  const myTeam = selectMyTeam(state)
  const team = myTeam && myTeam.toJS()
  //...
  return { user, me, myTeam, team /*, ...*/ }
 }

проблема вtoJSпо вызову, согласно документации:

Deeply converts this Keyed collection to equivalent native JavaScript Object.

toJSОбщий объект исходной структуры будет полностью клонирован, и все PureComponents будут повторно визуализированы. Взгляните на нашу текущую ситуацию:

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

Ниже удаленоtoJSПосле вызова:

Это намного лучше.

Суммировать

На этом этапе мы также можем сделать вывод, что производительность рендеринга React во многом зависит от степени детализации обновления.Когда наша функция рендеринга достаточно велика, мы можем делать только пошаговые обновления (основная проблема, решаемая Fiber и Time Нарезка) и точное обновление.

Чтобы добиться точных обновлений, мы должны хорошо обрабатывать изменения состояния. На самом деле, самый простой способ — сгладить состояние. Чем меньше уровень объекта, тем меньше проблем может возникнуть в нашем коде. Кроме того, насколько это возможноconnectПомещенные вне компонентов, требующих состояния, у нас все еще есть много компонентов, которые преждевременны.connect, а затем передать состояние слой за слоем черезpropsПередайте его вниз, это также вызвано глубоким уровнем объекта состояния (насколько глубоким я не буду делать скриншоты...). Когда состояние Redux обновляется (отправка), всеConnect(...)Компоненты обновляются в соответствии с их сопоставленным состоянием, чем раньшеconnectБолее вероятно, что компонент будет обновлен, а его дочерние компоненты не обрабатываются должным образом.shouldComponentUpdateБудет много бесполезных обновлений, потеря производительности зря.


References:

  1. Immutable Data Structures and JavaScript
  2. Reducers - Redux
  3. Map -- Immutable.js