Неизменяемость состояния — одна из ключевых тем в React. Её легко придерживаться, работая с примитивными типами данных, но с составными, такими как объекты и массивы, у неподготовленного пользователя могут возникнуть сложности. В этом уроке рассматриваются основные способы частичного обновления объектов и массивов.
Кроме примеров на чистом JS, будут продемонстрированы примеры с использованием библиотеки immutability-helper, которая создана для облегчения выполнения подобных операций. Она особенно актуальна при выполнении обновлений там, где код на JS получается слишком сложным.
Массивы
Массив: добавление
Самое простое — это добавление в массив:
const items = ['one', 'two', 'three']
const item = 'four'
const newItems = [...items, item]
// ['one', 'two', 'three', 'four'];
Если необходимо добавить элемент в начало, то нужно всего лишь поменять местами элементы массива:
const newItems = [item, ...items]
// ['four', 'one', 'two', 'three'];
Использование immutability-helper
import update from 'immutability-helper'
const state1 = ['x']
const state2 = update(state1, { $push: ['y'] }) // ['x', 'y']
Массив: удаление
Более интересный пример. Чтобы успешно выполнить удаление, нужно знать, что удалять. Это значит, что каждый элемент в коллекции должен иметь идентификатор. Для удаления используется старая добрая фильтрация.
const newItems = items.filter(item => item.id !== id)
Может возникнуть вопрос: откуда взялся идентификатор внутри обработчика? И здесь нам на помощь приходят замыкания.
https://codepen.io/hexlet/pen/JygbWP
Обратите внимание на способ задания обработчика: removeItem = (id) => (e) => {
и его использование onClick={this.removeItem(id)}
.
Использование immutability-helper
const index = 5
const newItems = update(items, { $splice: [[index, 1]] })
Удаление на чистом JS через фильтр — самый оптимальный способ. С использованием immutability-helper
получается сложно.
Массив: изменение
К сожалению, без дополнительных инструментов код решения будет слишком громоздким. Он приведён для ознакомления, но в реальном коде так делать не надо.
const index = items.findIndex(item => item.id === id)
const newItem = { ...items[index], value: 'another value' }
const newItems = [...items.slice(0, index), newItem, ...items.slice(index + 1)]
Думаю, не придётся вас убеждать в том, что это перебор :)
Использование immutability-helper
const collection = { children: ['zero', 'one', 'two'] }
const index = 1
const newCollection = update(collection, { children: { [index]: { $set: 1 } } })
// { children: ['zero', 1, 'two'] }
Как видно, этот способ значительно проще и чище. Рекомендуется к использованию.
Объекты
Объект: добавление
Так же просто, как и с массивом.
const items = { a: 1, b: 2 }
const newItems = { ...items, c: 3 }
// { a: 1, b: 2, c: 3 }
Либо, если ключ вычисляется динамически, нужно делать так:
const items = { a: 1, b: 2 }
const key = 'c'
const newItems = { ...items, [key]: 3 }
// { a: 1, b: 2, c: 3 }
Объект: удаление
На помощь приходит деструктуризация:
const { deletedKey, ...newState } = state
Использование immutability-helper
import update from 'immutability-helper'
const state = { a: 1, c: 3 }
const updatedState = update(state, {
$unset: ['c'],
})
// { a: 1 }
Объект: изменение
Абсолютно то же самое, что и добавление.
const items = { a: 1, b: 2 }
const newItems = { ...items, a: 3 }
// { a: 3, b: 2 }
Использование immutability-helper
const data = { a: 1, b: 2 }
const key = 'a'
const newData = update(data, { [key]: { $set: 3 } })
// { a: 3, b: 2 }
Глубокая вложенность
В примерах выше в основном можно обходиться стандартными средствами JS, и только в некоторых ситуациях удобнее пользоваться сторонними решениями. В реальном коде всё будет также, особенно если учитывать рекомендацию React и держать состояние максимально плоским. Но в некоторых ситуациях данные, которые нужно изменить, находятся не на поверхности, а в глубине структур. К сожалению, в этих ситуациях обычный JS-код будет раздуваться. И тут уже точно не обойтись без дополнительных библиотек.
import update from 'immutability-helper'
const myData = {
x: { y: { z: 5 } },
a: { b: [1, 2] },
}
const newData = update(myData, {
x: { y: { z: { $set: 7 } } },
a: { b: { $push: [9] } },
})
console.log(newData)
// => { x: { y: { z: 7 } }, a: { b: [ 1, 2, 9 ] } }
https://repl.it/@hexlet/js-react-immutability-helper
Аналоги
immutability-helper
— не единственная библиотека для подобных задач. Вот еще несколько популярных:
- immutable-js — основана на персистентных данных
- updeep — активно использует каррирование
- immerjs — пожалуй, самая популярная библиотека в JS для работы с неизменяемыми данными
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.