Изменчивое состояние в JavaScript

Состоянием называют практически любые временные данные, которые хранятся в памяти, те же переменные либо поля внутри объектов. Изменчивое состояние — один из источников сложности ПО, особенно в сочетании с объектной ориентацией. Но почему изменчивое состояние является такой большой проблемой?

Ограничения человеческого мозга

К сожалению, человеческий мозг довольно плохо работает с состоянием, ведь мы способны одновременно хранить в рабочей памяти лишь около 5 элементов. Программирование с изменяемым состоянием можно сравнить с умственным жонглированием. И если жонглировать 2-мя шарами относительно просто, то 3-мя и более — намного тяжелее. То же самое и с написанием кода. Ваш код станет надёжнее, а вы сами станете работать более продуктивно, если отбросите изменчивое состояние.

О реальных проблемах с изменчивым состоянием

Давайте глянем, как именно изменчивость способна сделать ваш код более проблематичным:

const increasePrice = (item, increaseBy) => {
  // Так не делайте никогда
  item.price += increaseBy;

  return item;
};

const oldItem = { price: 10 };

const newItem = increasePrice(oldItem, 3);

// Выводится «newItem.price 13»
console.log('newItem.price', newItem.price);

// Выводится «oldItem.price 13»
// Сюрпрайз!
console.log('oldItem.price', oldItem.price);

Как видите, ошибка очень тонка. Меняя аргументы функции, вы ненароком поменяли стоимость исходного элемента. Если предполагалось, что значение равняется десяти, на деле оно поменялось на 13.

Избежать этого можно, если создавать и возвращать новый объект (неизменность):

const increasePrice = (item, increaseBy) => ({
  ...item,
  price: item.price + increaseBy
});

const oldItem = { price: 10 };

const newItem = increasePrice(oldItem, 3);

// Выводится «newItem.price 13»
console.log('newItem.price', newItem.price);

// Выводится «oldItem.price 10»
// Как и ожидали
console.log('oldItem.price', oldItem.price);

Также учтите, что копирование посредством ES6-оператора «spread» выполнит не глубокую, а поверхностную копию, в результате чего не скопируется ни одно из вложенных свойств. То есть если у товара есть что-то типа item.seller.id, то seller нового товара всё так же будет ссылаться на старый товар.

Осталось посмотреть предлагаемую конфигурацию ESLint:

rules:
  fp/no-mutation: warn
  no-param-reassign: warn

Не применяйте push для массивов

Аналогичные проблемы присущи и изменениям массива с применением методов типа push:

const a = ['apple', 'orange'];
const b = a;

a.push('microsoft')

// ['apple', 'orange', 'microsoft']
console.log(a);

// ['apple', 'orange', 'microsoft']
// Сюрпрайз!
console.log(b);

Неожиданно, ведь вы, скорее всего, ожидали, что массив b останется прежним. Данной ошибки можно относительно просто избежать, если вы создадите вместо вызова push новый массив.

const newArray = [...a, 'microsoft'];

Источник