статья вgithubОткрытый исходный код, добро пожаловать, Fork, Star!
предисловие
ImmerЭто неизменяемая библиотека, написанная автором mobx.Основная реализация заключается в использовании прокси-сервера ES6, который реализует неизменяемую структуру данных js практически с минимальными затратами.Он прост в использовании, имеет небольшой размер и оригинальный дизайн. Требования к структуре данных.
К сожалению, в Интернете слишком мало полных документов, поэтому я написал один сам.Эта статья дает исчерпывающее объяснение Immer с идеями и процессами, близкими к реальному бою.
Проблемы с обработкой данных
Сначала определите исходный объект для использования в последующих примерах:
Сначала определитеcurrentState
Объекты, переменные используются в более поздних примерахcurrentState
Когда нет специальной декларации, это относится к этомуcurrentState
объект
let currentState = {
p: {
x: [2],
![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/8/17293174bf805c43~tplv-t2oaga2asx-image.image)
},
}
При каких обстоятельствах исходный объект может быть случайно изменен?
// Q1
let o1 = currentState;
o1.p = 1; // currentState 被修改了
o1.p.x = 1; // currentState 被修改了
// Q2
fn(currentState); // currentState 被修改了
function fn(o) {
o.p1 = 1;
return o;
};
// Q3
let o3 = {
...currentState
};
o3.p.x = 1; // currentState 被修改了
// Q4
let o4 = currentState;
o4.p.x.push(1); // currentState 被修改了
Решение модификации объекта ссылочного типа
- Глубокое копирование, но стоимость глубокого копирования высока, что повлияет на производительность;
- ImmutableJS, очень хорошая библиотека неизменяемых структур данных, может решить вышеуказанные проблемы, но, по сравнению с Immer, ImmutableJS имеет два основных недостатка:
- Пользователи должны изучить его метод работы со структурой данных, а метод работы с использованием собственных объектов, предоставляемых Immer, прост и удобен в использовании;
- Результат его работы должен пройти
toJS
Только метод может получить собственный объект, что заставляет при работе с объектом всегда обращать внимание на то, является ли операция собственным объектом или возвращаемым результатом ImmutableJS.Если вы не будете осторожны, возникнут непредвиденные ошибки.
Похоже, нас не устраивают известные на данный момент решения, так в чем же гениальность Иммера?
введение функции погружения
установить иммерс
Если вы хотите делать хорошие вещи, вы должны сначала отточить свои инструменты, и установка Immer является первоочередной задачей.
npm i --save immer
Как immer решает эти неприятные проблемы
Исправить Q1, Q3
import produce from 'immer';
let o1 = produce(currentState, draftState => {
draftState.p.x = 1;
})
Fix Q2
import produce from 'immer';
fn(currentState);
function fn(o) {
return produce(o, draftState => {
draftState.p1 = 1;
})
};
Fix Q4
import produce from 'immer';
let o4 = produce(currentState, draftState => {
draftState.p.x.push(1);
})
Это очень просто в использовании? Благодаря небольшому тесту мы получили простое представление о Immer. Ниже приводится введение в часто используемый API Immer.
Концептуальная записка
В Immer используется не так много понятий.Здесь задействованные понятия перечислены первыми.Если вы столкнетесь с понятиями, которые не понимаете в процессе чтения этой статьи, вы можете проверить их здесь в любое время.
-
currentState
Исходное состояние управляемого объекта -
draftState
Состояние черновика, сгенерированное из currentState, который является прокси для currentState, и любые изменения, внесенные в draftState, будут регистрироваться и использоваться для создания nextState. Во время этого процесса текущее состояние не будет затронуто -
nextState
В конечном состоянии DraftState -
производить
функция для генерации nextState или производителя -
продюсер продюсер
Генерируется product, используется для создания nextState, каждый раз выполняет одну и ту же операцию -
машина для производства рецептов
Функции для управления draftState
Введение в распространенные API
Перед использованием Immer убедитесь, чтоimmer
Пакеты импортируются в модули
import produce from 'immer'
or
import { produce } from 'immer'
Эти два метода ссылки, продукция точно такие же
produce
Примечания: появляетсяPatchListener
Пропустите сначала, будет представлено в последующих главах
Первый способ использования:
грамматика:
produce(currentState, recipe: (draftState) => void | draftState, ?PatchListener): nextState
Пример 1:
let nextState = produce(currentState, (draftState) => {
})
currentState === nextState; // true
Пример 2:
let currentState = {
a: [],
p: {
x: 1
}
}
let nextState = produce(currentState, (draftState) => {
draftState.a.push(2);
})
currentState === nextState // false
currentState.a === nextState.a; // false
currentState.p === nextState.p; // true
Видно, что изменения в draftState будут отражены в nextState. Структура, используемая Immer, является общей, а nextState разделяет неизмененную часть с currentState в структуре.Общий эффект показан на рисунке (заимствовано из статьи Immutable в анимации, вторжении и удалении):
Функция автоматической заморозки
Immer также делает очень хитрую внутреннюю вещь, то есть nextState, созданный продуктом, замораживается (замораживается), (Immer внутренне используетObject.freeze
метод, замораживает только измененную часть nextState по сравнению с currentState), поэтому при непосредственном изменении nextState будет сообщено об ошибке.
Это делает данные nextState действительно неизменяемыми.
Пример:
const currentState = {
p: {
x: [2],
},
};
const nextState = produce(currentState, draftState => {
draftState.p.x.push(3);
});
console.log(nextState.p.x); // [2, 3]
nextState.p.x = 4;
console.log(nextState.p.x); // [2, 3]
nextState.p.x.push(5); // 报错
2-й способ использования
Используйте характеристики функций высшего порядка для создания производителя производителя.
грамматика:
produce(recipe: (draftState) => void | draftState, ?PatchListener)(currentState): nextState
пример:
let producer = produce((draftState) => {
draftState.x = 2
});
let nextState = producer(currentState);
возвращаемое значение рецепта
Независимо от того, имеет ли рецепт возвращаемое значение, процесс генерации nextState отличается:
Когда рецепт не имеет возвращаемого значения: nextState генерируется в соответствии с draftState;
Когда рецепт имеет возвращаемое значение: nextState генерируется в соответствии с возвращаемым значением функции рецепта;
let nextState = produce(currentState, (draftState) => {
return {
x: 5
}
}
)
console.log(nextState); // {x: 5}
На этом этапе nextState больше не генерируется draftState, а возвращается значением recipe.
это в рецепте
внутри функции рецептаthis
Укажите на draftState , который должен изменитьthis
Эффект такой же, как при изменении параметра draftState рецепта.
Примечание. Функция рецепта здесь не может быть стрелочной функцией, если это стрелочная функция,this
не могу указать на draftState
produce(currentState, function(draftState){
// 此处,this 指向 draftState
draftState === this; // true
})
Функция патча PATCH
С помощью этой функции можно облегчить детальную отладку и отслеживание кода, узнать о каждой модификации draftState, а также реализовать путешествие во времени.
В Immer объект патча выглядит следующим образом:
interface Patch {
op: "replace" | "remove" | "add" // 一次更改的动作类型
path: (string | number)[] // 此属性指从树根到被更改树杈的路径
value?: any // op为 replace、add 时,才有此属性,表示新的赋值
}
грамматика:
produce(
currentState,
recipe,
// 通过 patchListener 函数,暴露正向和反向的补丁数组
patchListener: (patches: Patch[], inversePatches: Patch[]) => void
)
applyPatches(currentState, changes: (patches | inversePatches)[]): nextState
пример:
import produce, { applyPatches } from "immer"
let state = {
x: 1
}
let replaces = [];
let inverseReplaces = [];
state = produce(
state,
draftState => {
draftState.x = 2;
draftState.y = 2;
},
(patches, inversePatches) => {
replaces = patches.filter(patch => patch.op === 'replace');
inverseReplaces = inversePatches.filter(patch => patch.op === 'replace');
}
)
state = produce(state, draftState => {
draftState.x = 3;
})
console.log('state1', state); // { x: 3, y: 2 }
state = applyPatches(state, replaces);
console.log('state2', state); // { x: 2, y: 2 }
state = produce(state, draftState => {
draftState.x = 4;
})
console.log('state3', state); // { x: 4, y: 2 }
state = applyPatches(state, inverseReplaces);
console.log('state4', state); // { x: 1, y: 2 }
state.x
Значение 4-кратного распечатывания результатов:3、2、4、1
, осуществляет путешествия во времени,
можно распечатать отдельноpatches
а такжеinversePatches
Смотреть,
patches
Данные следующие:
[
{
op: "replace",
path: ["x"],
value: 2
},
{
op: "add",
path: ["y"],
value: 2
},
]
inversePatches
Данные следующие:
[
{
op: "replace",
path: ["x"],
value: 1
},
{
op: "remove",
path: ["y"],
},
]
видимый,patchListener
Внутри операции с данными записываются и хранятся как записи прямых операций и записи обратных операций для нашего использования.
До сих пор мы представили общие функции и API-интерфейсы Immer.
Далее давайте посмотрим, как использовать Immer для повышения эффективности разработки проектов React и Redux.
Изучение оптимизации реактивных проектов с помощью immer
Сначала определитеstate
Объекты, переменные используются в более поздних примерахstate
или посетитьthis.state
Когда нет специальной декларации, это относится к этомуstate
объект
state = {
members: [
{
name: 'ronffy',
age: 30
}
]
}
бросить спрос
как определено вышеstate
, давайте сначала бросим требование, чтобы следующее объяснение могло быть нацелено:
мемберы 1-й член мемберов, возраст увеличен на 1 год
Оптимизируйте метод setState
Пример ошибки
this.state.members[0].age++;
Такие ошибки допускают только некоторые начинающие студенты, основная причина в том, что эта операция настолько удобна, что они забывают правила рабочего состояния.
Вот правильный способ сделать это
Первый метод реализации setState
const { members } = this.state;
this.setState({
members: [
{
...members[0],
age: members[0].age + 1,
},
...members.slice(1),
]
})
Второй метод реализации setState
this.setState(state => {
const { members } = state;
return {
members: [
{
...members[0],
age: members[0].age + 1,
},
...members.slice(1)
]
}
})
Вышеуказанные два метода реализацииsetState
Вы должны быть знакомы с двумя способами его использования. Далее посмотрим, если для ее решения использовать Immer, какой будет фейерверк?
обновить состояние с помощью immer
this.setState(produce(draftState => {
draftState.members[0].age++;
}))
Разве это не сразу намного меньше кода и легче читать.
Оптимизировать редукторы
Расширенное использование продуктов иммера
Прежде чем мы начнем формальное исследование, давайте взглянем на продукцию2-й способ использованияРасширенное использование:
пример:
let obj = {};
let producer = produce((draftState, arg) => {
obj === arg; // true
});
let nextState = producer(currentState, obj);
По сравнению с примером второго употребления продукта определено еще одноobj
объект и передал его как второй параметр метода производителя; вы можете видеть, что второй параметр функции обратного вызова рецепта в продукте такой же, какobj
Объекты указывают на один и тот же блок памяти.
Хорошо, после того, как мы узнаем об этом расширенном использовании продукта, давайте посмотрим, какой эффект он может сыграть в Redux?
Как обычный редуктор решает требования, указанные выше
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_AGE':
const { members } = state;
return {
...state,
members: [
{
...members[0],
age: members[0].age + 1,
},
...members.slice(1),
]
}
default:
return state
}
}
Установите иммер, редуктор, как писать
const reducer = (state, action) => produce(state, draftState => {
switch (action.type) {
case 'ADD_AGE':
draftState.members[0].age++;
}
})
Как видите, благодаря product объем нашего кода значительно упростился;
Однако нетрудно заметить, что код может быть более элегантным, если использовать характеристики продукта для создания производителя:
const reducer = produce((draftState, action) => {
switch (action.type) {
case 'ADD_AGE':
draftState.members[0].age++;
}
})
Хорошо, до сих пор был объяснен метод оптимизации редуктора Иммера.
Использование Immer очень гибкое, и есть несколько других расширений API, проведите много исследований, я думаю, вы можете найти больше других замечательных применений Immer!