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

let newState = {
  ...oldState,
  field1: {
    ...oldState.field1,
    field2: "someNewValue"
  },
}

В приведенном выше коде мы изменяем значение field2 oldState, создавая новое состояние и устанавливая новое значение field2. Значение и ссылка oldState остаются прежними.

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

Разница между значением и ссылкой

Значение переменной — это «семантическое» значение того, что содержит эта переменная. Например, в приведенном ниже примере кода семантика того, что содержится в var1 и var2 , одинакова, поэтому мы можем сказать, что их значения одинаковы. Однако значение var3 отличается, поскольку «семантика» того, что он содержит, отличается.

let var1 = { name: "John", age: 20 }
let var2 = { name: "John", age: 20 }

let var3 = { name: "May", age: 30 }

Когда мы говорим о ссылке, мы имеем в виду (каламбур!) адрес памяти, где что-то хранится. Таким образом, в приведенном выше примере адрес памяти объекта, на который ссылается var1, отличается от адреса памяти объекта, на который ссылается var2. Другими словами, var1 указывает на адрес памяти, отличный от var2. Следовательно, их ссылки различны, хотя их значения одинаковы!

Единственный способ, которым две переменные могут иметь одну и ту же ссылку, — это когда они обе указывают на один и тот же адрес памяти. Таким образом, в приведенном ниже коде var4 и var5 имеют одну и ту же ссылку:

let var4 = { name: "Jeremy", age: 50 }
let var5 = var4

Если мы сделаем var5.name = “Mary”, то значением var4.name также будет «Мария».

Исходя из этого понимания, мы можем сделать вывод:

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

Повторный рендеринг компонентов реакции

Возвращаясь к Redux и React, React захочет перерендерить компонент только в том случае, если значение реквизита или состояние изменилось. Чтобы узнать, изменилось ли их значение, мы должны провести «глубокое сравнение» — рекурсивно проверить все поля внутри состояния и реквизита, чтобы увидеть, изменилось ли какое-либо из них.

Большие приложения обычно имеют очень глубокую структуру состояния при использовании редукции — несколько вложенных уровней (на счету 100 или даже 1000). Выполнение здесь глубокого сравнения, возможно, несколько раз в секунду, замедлит работу пользовательского интерфейса. С другой стороны, если мы делаем «поверхностное сравнение» (где мы только проверяем, изменились ли значения полей первого уровня), это будет намного быстрее, но мы можем пропустить обновления — нарушить логику приложения. Пример того, как мы можем пропустить обновления при поверхностном сравнении, представлен ниже:

let oldState = {
  name: "John",
  age: 20,
  profession: {
    title: "Software Engineer",
    organization: "SuperTokens.io"
  }
}
let newState = oldState
newState.profession.title = "Senior Software Engineer"

// Shallow comparison. upto level one
if (newState !== oldState || oldState.name !== newState.name || oldState.age !== newState.age || oldState.profession !== newState.profession) {
// Update UI
}

Оптимизация с помощью правила неизменности

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

Основываясь на том, что мы видели в предыдущих разделах, мы знаем, что «если ссылки на две переменные (в данном случае переменные состояния) разные, их значения могут быть, а могут и не совпадать». Что, если мы изменим это на «тогда и только тогда, когда ссылки на две переменные (переменные состояния в данном случае) разные, мы должны предположить, что их значения различны». Что происходит сейчас?

Если приведенное выше изменение принудительно, то чтобы узнать, изменилось ли значение состояния, мы можем просто выполнить проверку ссылки, например oldState === newState (если это false, то ссылка изменилась). Если ссылка изменилась, мы можем предположить, что значения должны были измениться, и запустить рендеринг. Если нет, то не перерисовываем.

Чтобы обеспечить соблюдение этого предположения, мы никогда не должны напрямую изменять поля внутри oldState. Вместо этого мы всегда должны создавать новую копию oldStatenewState), как мы показали в начале этой статьи, и вносить изменения в newState. Поскольку newState — это новый объект, его ссылка всегда будет отличаться от ссылки на oldState. Это известно как обеспечение неизменности состояния — именно то, что Redux заставляет делать своих пользователей!

Заключение

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

Написано ребятами из SuperTokens — надеюсь, вам понравилось! Мы всегда доступны на нашем Discord сервере. Присоединяйтесь к нам, если у вас есть какие-либо вопросы или вам нужна помощь.