Следует отметить, что в вашем примере мутация не вызовет никаких проблем, если вы просто выполните соответствующее копирование уровня на основе объекта.
Для abc
, похожего на { a: 1, b: 2, c: 3 }
, вы можете просто сделать поверхностное копирование, в то время как для вложенного объекта { a: { name: 1 } }
вам придется его глубоко копировать, но вы все равно можете сделать это явно, без каких-либо библиотек или чего-либо еще.
{
...state,
a: {
...action.a
}
}
В качестве альтернативы вы можете предотвратить мутацию с помощью eslint-plugin-immutable , которая вынудит программиста не писать такой код.
Как вы можете видеть в описании плагина ESLint выше для no-mutation
правила :
Это правило так же эффективно, как и использование Object.freeze () для предотвращения мутаций в редукторах Redux. Однако это правило не требует затрат времени выполнения. Хорошей альтернативой мутации объектов является использование синтаксиса распространения объектов, входящего в ES2016.
Еще один относительно простой способ (без использования некоторой библиотеки неизменяемости) активно предотвращать мутации - заморозить ваше состояние при каждом обновлении.
export default function(state = initialState, action) {
switch(action.type) {
case SOME_ACTION:
return Object.freeze({
...state,
section1: Object.freeze(action.payload.abc)
});
}
return state;
}
Вот пример:
const object = { a: 1, b: 2, c : 3 };
const immutable = Object.freeze(object);
object.a = 5;
console.log(immutable.a); // 1
Object.freeze
- это мелкая операция, поэтому вам придется вручную заморозить оставшуюся часть объекта или использовать библиотеку, например deep-freeze .
Приведенный выше подход возлагает на вас ответственность за защиту мутации.
Недостаток этого подхода состоит в том, что он увеличивает необходимые когнитивные усилия для программиста, делая явную защиту от мутаций и, таким образом, более подвержен ошибкам (особенно в большой кодовой базе).
При глубоком замораживании могут также наблюдаться некоторые проблемы с производительностью, особенно если вы используете библиотеку, созданную для тестирования / обработки различных крайних случаев, которые ваше конкретное приложение, возможно, никогда не представит.
Более масштабируемый подход заключается во внедрении шаблона неизменяемости в логику кода, который естественным образом подталкивает шаблоны кодирования к неизменяемым операциям.
Один из подходов заключается в использовании неизменяемого JS , когда сами структуры данных построены таким образом, что операция над ними всегда создает новый экземпляр и никогда не мутирует.
import { Map } from 'immutable';
export default function(state = Map(), action) {
switch(action.type) {
case SOME_ACTION:
return state.merge({ section1: action.payload.abc });
// this creates a new immutable state by adding the abc object into it
// can also use mergeDeep if abc is nested
}
return state;
}
Другой подход заключается в использовании immer
, который скрывает неизменность за кулисами и дает изменчивый apis, следуя принципу copy-on-write .
import produce from 'immer'
export default = (state = initialState, action) =>
produce(state, draft => {
switch (action.type) {
case SOME_ACTION:
draft.section1 = action.section1;
})
}
Эта библиотека может вам пригодиться, если вы конвертируете существующее приложение, в котором, вероятно, много кода, выполняющего мутацию.
Недостаток библиотеки неизменяемости состоит в том, что она увеличивает барьер для доступа к вашей базе кода для людей, незнакомых с библиотекой, потому что теперь каждый должен изучать ее.
При этом согласованный шаблон кодирования уменьшает когнитивные усилия (все используют один и тот же шаблон), а также уменьшает хаос-фактор кода (мешает людям все время изобретать свои собственные шаблоны), явно ограничивая способ построения кода. , Это, естественно, приведет к уменьшению количества ошибок и ускорению разработки.