Spec-Zone.ru › Redux 3

Шаблоны неизменяемых обновлений

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

Обновление вложенных объектов

Ключ к обновлению вложенных данных заключается в том, что каждый уровень вложенности должен быть скопирован и обновлен соответствующим образом. Это часто является сложным понятием для тех, кто изучает Redux, и существуют некоторые специфические проблемы, которые часто возникают при попытке обновить вложенные объекты. Они приводят к случайному прямому изменению, и их следует избегать.

Ошибка №1: Новые переменные, указывающие на те же объекты

Определение новой переменной не создает новый фактический объект — она только создает другую ссылку на тот же объект. Примером этой ошибки является:

function updateNestedState(state, action) {
    let nestedState = state.nestedState;
    // ERROR: this directly modifies the existing object reference - don't do this!
    nestedState.nestedField = action.data;

    return {
        ...state,
        nestedState
    };
}

Эта функция правильно возвращает поверхностную копию объекта состояния верхнего уровня, но поскольку переменная nestedState по-прежнему указывала на существующий объект, состояние было изменено напрямую.

Ошибка №2: Только создание поверхностной копии одного уровня

Другая распространенная версия этой ошибки выглядит так:

function updateNestedState(state, action) {
    // Problem: this only does a shallow copy!
    let newState = {...state};

    // ERROR: nestedState is still the same object!
    newState.nestedState.nestedField = action.data;

    return newState;
}

Создание поверхностной копии верхнего уровня недостаточно — объект nestedState также должен быть скопирован.

Правильный подход: Копирование всех уровней вложенных данных

К сожалению, процесс правильного применения неизменяемых обновлений к глубоко вложенному состоянию может легко стать громоздким и трудночитаемым. Вот пример обновления state.first.second[someId].fourth:

function updateVeryNestedField(state, action) {
    return {
        ...state,
        first : {
            ...state.first,
            second : {
                ...state.first.second,
                [action.someId] : {
                    ...state.first.second[action.someId],
                    fourth : action.someValue
                }
            }
        }
    }
}

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

Вставка и удаление элементов в массивах

Обычно содержимое массива JavaScript изменяется с помощью мутационных функций, таких как push, unshift, и splice. Поскольку мы не хотим изменять состояние напрямую в редукторах, их обычно следует избегать. Из-за этого вы можете увидеть поведение "вставки" или "удаления", написанное так:

function insertItem(array, action) {
    return [
        ...array.slice(0, action.index),
        action.item,
        ...array.slice(action.index)
    ]
}

function removeItem(array, action) {
    return [
        ...array.slice(0, action.index),
        ...array.slice(action.index + 1)
    ];
}

Однако помните, что ключевым моментом является то, что исходная ссылка в памяти не изменяется. Пока мы создаем копию, мы можем безопасно изменить копию. Обратите внимание, что это верно как для массивов, так и для объектов, но вложенные значения все равно должны обновляться по тем же правилам.

Это означает, что мы также можем написать функции вставки и удаления так:

function insertItem(array, action) {
    let newArray = array.slice();
    newArray.splice(action.index, 0, action.item);
    return newArray;
}

function removeItem(array, action) {
    let newArray = array.slice();
    newArray.splice(action.index, 1);
    return newArray;
}

Функция удаления также может быть реализована так:

function removeItem(array, action) {
    return array.filter( (item, index) => index !== action.index);
}

Обновление элемента в массиве

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

function updateObjectInArray(array, action) {
    return array.map( (item, index) => {
        if(index !== action.index) {
            // This isn't the item we care about - keep it as-is
            return item;
        }

        // Otherwise, this is the one we want - return an updated value
        return {
            ...item,
            ...action.item
        };    
    });
}

Библиотеки полезных программ для неизменяемых обновлений

Поскольку написание кода неизменяемых обновлений может стать утомительным, существует ряд библиотек полезных программ, которые пытаются абстрагировать этот процесс. Эти библиотеки отличаются API и использованием, но все они пытаются предоставить более короткий и лаконичный способ написания этих обновлений. Некоторые из них, например, dot-prop-immutable, используют строковые пути для команд:

state = dotProp.set(state, `todos.${index}.complete`, true)

Другие, такие как immutability-helper (ветвь ныне устаревшего дополнения React Immutability Helpers), используют вложенные значения и вспомогательные функции:

var collection = [1, 2, {a: [12, 17, 15]}];
var newCollection = update(collection, {2: {a: {$splice: [[1, 1, 13, 14]]}}});

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

Список многих полезных программ для неизменяемых обновлений можно найти в разделе Неизменяемые данные#Полезные программы для неизменяемых обновлений каталога Redux Addons Catalog.

© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API