Интенсивное чтение исходного кода "Immer.js"

внешний интерфейс JavaScript React.js Immutable.js

Склад сгущенки на этой неделеimmer.

1. Введение

Immer — проект, ставший популярным в последнее время.MobxавторMweststrateНИОКР.

Студенты, знакомые с mobx, могут обнаружить, что Immer — это Mobx более низкого уровня, который обладает функциями Mobx и может быть объединен в любую структуру потока данных, которая очень элегантна в использовании.

2 Обзор

Проблемный Неизменный

Проблема, которую хочет решить Иммер, состоит в том, чтобы использовать метапрограммирование для упрощения сложности использования Immutable. Например, давайте напишем чистую функцию:

const addProducts = products => {
  const cloneProducts = products.slice()
  cloneProducts.push({ text: "shoes" })
  return cloneProducts
}

Хоть код и не сложный, писать его все равно больно. мы должны поставитьproductsсделайте копию и позвоните сноваpushфункция изменения новогоcloneProducts, и вернуть его.

Если js изначально поддерживает Immutable, его можно использовать напрямую.push! Да, иммумер делает js теперь поддерживает:

const addProducts = produce(products => {
  products.push({ text: "shoes" })
})

Это весело, эти двоеaddProductsФункции точно такие же, и все они чистые функции.

неудобный setState

Мы все знаем, что в среде реагированияsetStateФункциональное письмо поддерживается:

this.setState(state => ({
  ...state,
  isShow: true
}))

В сочетании с синтаксисом деструктурирования это все еще так элегантно написано. А как насчет немного более сложных данных? Будем молча терпеть "плохой Immutable":

this.setState(state => {
  const cloneProducts = state.products.slice()
  cloneProducts.push({ text: "shoes" })
  return {
    ...state,
    cloneProducts
  }
})

Однако с Immer все иначе:

this.setState(produce(state => (state.isShow = true)))

this.setState(produce(state => state.products.push({ text: "shoes" })))

Удобное каррирование

Выше описаны преимущества поддержки Immer для каррирования. Таким образом, мы также можем напрямую использовать два параметра одновременно:

const oldObj = { value: 1 }
const newObj = produce(oldObj, draft => (draft.value = 2))

Это Immer: создайте следующее неизменное состояние, изменив текущее.

3 Интенсивное чтение

Хотя автор уже проводил некоторые исследования в этой области, например, создание библиотеки Mutable to Immutable:dob-redux, но Immer действительно потрясающий, Immer — это головоломка более низкого уровня, его можно подключить к любой структуре потока данных в качестве функционального расширения, я должен восхищаться Mweststrate, действительно дальновидным.

Поэтому автор внимательно прочитал его исходный код и взял всех на понимание Immer с принципиальной точки зрения.

Иммер - это поддержка карри,Инструменты, которые поддерживают только синхронные вычисления, поэтому он очень подходит в качестве редуктора для редукса.

Immer также поддерживает прямое возвращаемое значение.Эта функция относительно проста, поэтому в этой статье вся обработка возвращаемого значения будет пропущена. PS: mutable и return не могут одновременно возвращать разные объекты, иначе непонятно, какая модификация действительна.

Каррирование здесь не будет раскрыто, подробнее см.curry. мы видимproduceЧасть обратного вызова функции:

produce(obj, draft => {
  draft.count++
})

objявляется обычным объектом, что черная магия должна появиться вdraftНа объекте, погрузиться вdraftВсе свойства объекта контролируются.

Итак, общая идея такова:draftдаobjагент, даdraftИзменяемые модификации будут перетекать в кастомныеsetterфункция, она не изменяет значение исходного объекта, но рекурсивный родитель продолжает неглубокое копирование и, наконец, возвращает новый объект верхнего уровня, какproduceВозвращаемое значение функции.

Создать прокси

Первый шаг —objПеревести вdraftНа этом шаге, чтобы повысить эффективность Immutable, нам нужна дополнительная информация, поэтому мыobjИнкапсулирован в прокси-объект, содержащий дополнительную информацию:

{
  modified, // 是否被修改过
  finalized, // 是否已经完成(所有 setter 执行完,并且已经生成了 copy)
  parent, // 父级对象
  base, // 原始对象(也就是 obj)
  copy, // base(也就是 obj)的浅拷贝,使用 Object.assign(Object.create(null), obj) 实现
  proxies, // 存储每个 propertyKey 的代理对象,采用懒初始化策略
}

На этом прокси-объекте настраиваемыйgetter setter, а затем бросить его прямо вproduceвоплощать в жизнь.

getter

produceФункция обратного вызова содержитmutableкод. Итак, теперь вход становитсяgetterа такжеsetter.

getterОн в основном используется для ленивой инициализации прокси-объекта, то есть при доступе к подсвойству прокси-объекта будет создан его прокси-объект.

Итак, более абстрактно, например, исходный объект выглядит следующим образом:

{
  a: {},
  b: {},
  c: {}
}

Итак, изначально,draftдаobjпрокси, поэтому доступdraft.a draft.b draft.cможет вызватьgetter setter, введите пользовательскую логику обработки. но даdraft.a.xНет возможности слушать, потому что прокси может слушать только один слой.

Ленивая инициализация прокси должна решить эту проблему, когда доступ кdraft.a, обычайgetterНовая цель была тихо сгенерированаdraft.aпрокси объектаdraftA,следовательноdraft.a.xэквивалентно посещениюdraftA.x, поэтому он может рекурсивно отслеживать все свойства объекта.

В то же время, если код обращается только кdraft.a, то он будет генерироваться только в памятиdraftAиграет роль,b cПоскольку доступ к свойствам не осуществляется, нет необходимости тратить ресурсы на создание прокси.draftB draftC.

Конечно, Immer сделал некоторые оптимизации производительности, и когда объект изменяется (modified), чтобы получить егоcopyобъекта, чтобы обеспечитьbaseОн неизменен и не будет здесь раскрываться.

setter

когда правильноdraftПри изменении,baseТо есть исходное значение неглубоко копируется и сохраняется вcopyсвойства, при этомmodifiedсвойство установлено наtrue. Это завершает самый важный неизменяемый процесс, а неглубокая копия не очень требовательна к производительности, плюс это неглубокая копия по запросу, поэтому производительность Immer в порядке.

В то же время для того, чтобы объекты всей ссылки были новыми объектами,parentАтрибут рекурсивно относится к родителю и продолжает неглубоко копироваться до тех пор, пока весь объект ссылки от конечного узла до корневого узла не будет заменен новым.

законченныйmodifiedПри изменении другого свойства объекта новое значение сохраняется вcopyна объекте.

Создание неизменяемых объектов

при исполненииproduceПосле внесения всех изменений пользователем (поэтому Immer не поддерживает асинхронность), еслиmodifiedсобственностьfalse, указывая на то, что пользователь вообще не изменил объект, а затем вернуться к оригиналу напрямуюbaseхарактеристики.

еслиmodifiedсобственностьtrue, указывая на то, что объект был изменен, вернутьcopyхарактеристики. ноsetterПроцесс рекурсивный,draftдочерние объектыdraft(включает в себяbase copy modifiedпрокси для дополнительных свойств), мы должны выполнять рекурсию слой за слоем, чтобы получить реальное значение.

Итак, на данном этапе всеdraftизfinalizedобеfalse,copyМожет быть многоdraftсвойства, поэтому рекурсивныеbaseа такжеcopyПодатрибуты , если они одинаковые, возвращаются напрямую; если они разные, повторяют весь процесс один раз (начиная с первой строки этого подраздела).

Последний возвращаемый объект - этоbaseнекоторые свойства (немодифицированных частей) иcopyНекоторые свойства (модифицированные части) окончательно сшиты вместе. последнее использованиеfreezeзаморозитьcopyсвойства, воляfinalizedсвойство установлено наtrue.

В этот момент генерируется возвращаемое значение, и мы сохраняем окончательное значение вcopyсвойство и заморозить его, вернув значение Immutable.

Таким образом, Immer делает невероятное: создает следующее неизменное состояние, изменяя текущее.

Прочитав исходный код, я обнаружил, что Immer действительно может поддерживать асинхронность, если он поддерживает функцию product для возврата Promise. Самая большая проблема в том, что в итоге проксиrevokeДля очистки требуется помощь глобальных переменных, что препятствует поддержке Immer асинхронности.

4 Резюме

Прочитав это, если вы чувствуете, что этого недостаточно, вы можете взглянутьredux-boxЭта библиотека использует immer + redux для решения проблемы избыточности редуктора.returnЭта проблема.

В то же время мы также начали думать и проектировать новый фреймворк потока данных, которым автор поделится на Салоне технологий Ctrip 24 марта 2018 года."Прекрасные лекции по интерфейсной платформе потока данных mvvm", чтобы поделиться опытом исследования различных наборов технологических решений для потоков данных, появившихся в последние годы Заинтересованные студенты могут зарегистрироваться.

еще 5 обсуждений

Адрес обсуждения:Интенсивное чтение исходного кода "Immer.js" · Выпуск №68 · dt-fe/weekly

Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, публикуется каждую пятницу.