[TOC]
immer源码解析
入口
src/immer.ts
// src/immer.ts#L23
const immer = new Immer()
// src/immer.ts#L44
export const produce: IProduce = immer.produce
produce
例子
// produce(baseState, recipe: (draftState) => void): nextState
import produce from "immer"
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({title: "Tweet about it"})
draftState[1].done = true
})
produce中对象的更改不会导致原值更改
expect(baseState.length).toBe(2)
expect(nextState.length).toBe(3)
// same for the changed 'done' prop
expect(baseState[1].done).toBe(false)
expect(nextState[1].done).toBe(true)
// unchanged data is structurally shared
expect(nextState[0]).toBe(baseState[0])
// ...but changed data isn't.
expect(nextState[1]).not.toBe(baseState[1])
源码
接下来看源码,看着不长,但是引用的函数较多
export class Immer implements ProducersFns {
......
/**
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
// curried invocation
// base 和 recipe参数 互换
// 上述例子走不到这个判断
if (typeof base === "function" && typeof recipe !== "function") {
const defaultBase = recipe
recipe = base
const self = this
return function curriedProduce(
this: any,
base = defaultBase,
...args: any[]
) {
return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
}
}
// 错误信息 6: "The first or second argument to `produce` must be a function"
if (typeof recipe !== "function") die(6)
// 错误信息 7: "The third argument to `produce` must be a function or undefined"
if (patchListener !== undefined && typeof patchListener !== "function")
die(7)
let result
// 判断对象是否是是Draftable
if (isDraftable(base)) {
// 生成作用域,一个对象对应一个作用域
// this是Immer类的实例
const scope = enterScope(this)
// 添加proxy代理,这个是重点之一
const proxy = createProxy(this, base, undefined)
let hasError = true
try {
// 执行回调
result = recipe(proxy)
hasError = false
} finally {
// finally 而不是 catch + rethrow 更好地保留原始堆栈
// revokeScope:终止当前scope的所有proxy,将currentScope变量赋值为scope.parent_
if (hasError) revokeScope(scope)
else leaveScope(scope)
}
if (typeof Promise !== "undefined" && result instanceof Promise) {
return result.then(
result => {
usePatchesInScope(scope, patchListener)
return processResult(result, scope)
},
error => {
// 同上
revokeScope(scope)
throw error
}
)
}
usePatchesInScope(scope, patchListener)
return processResult(result, scope)
} else if (!base || typeof base !== "object") {
result = recipe(base)
if (result === undefined) result = base
if (result === NOTHING) result = undefined
if (this.autoFreeze_) freeze(result, true)
if (patchListener) {
const p: Patch[] = []
const ip: Patch[] = []
getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
patchListener(p, ip)
}
return result
} else die(21, base)
}
isDraftable
对类型的判断,没啥注释,一看就懂
export function isDraftable(value: any): boolean {
if (!value) return false
return (
isPlainObject(value) ||
Array.isArray(value) ||
!!value[DRAFTABLE] ||
!!value.constructor[DRAFTABLE] ||
isMap(value) ||
isSet(value)
)
}
-
isPlainObject:
const objectCtorString = Object.prototype.constructor.toString()
export function isPlainObject(value: any): boolean {
if (!value || typeof value !== "object") return false
const proto = Object.getPrototypeOf(value)
if (proto === null) {
return true
}
const Ctor =
Object.hasOwnProperty.call(proto, "constructor") && proto.constructor
if (Ctor === Object) return true
return (
typeof Ctor == "function" &&
// 字符串的比较主要是为了解决不同js环境,例如iframe
Function.toString.call(Ctor) === objectCtorString
)
}
-
DRAFTABLE:
export const DRAFTABLE: unique symbol = hasSymbol
? Symbol.for("immer-draftable")
: ("__$immer_draftable" as any)
-
isMap\isSet:
// hasMap\hasSet 是否有map set的构造函数
export function isMap(target: any): target is AnyMap {
return hasMap && target instanceof Map
}
export function isSet(target: any): target is AnySet {
return hasSet && target instanceof Set
}
enterScope
let currentScope: ImmerScope | undefined
export function enterScope(immer: Immer) {
return (currentScope = createScope(currentScope, immer))
}
function createScope(
parent_: ImmerScope | undefined,
immer_: Immer
): ImmerScope {
return {
// 保存着每次通过createProxy生成的proxy对象
// 例1
drafts_: [],
parent_,
immer_,
// 每当修改后的draft包含来自另一个范围的draft时,我们需要防止自动冻结,以便最终确定无主draft。
canAutoFreeze_: true,
unfinalizedDrafts_: 0
}
}
-
例1
const obj = {a: {c: 1}, b: 1}
const produce = immer.produce
const p2 = produce(a, draft => {
draft.a.c = 3
})
// 此时
// scope.drafts_=>[Proxy,Proxy]
// 数组第一项proxy是代理变量obj创建的,第二项是代理obj.a创建的
createProxy
重点之一,为数据添加proxy
src/core/proxy.ts#L50
公共参数和函数
// src/types/types-internal.ts#L20
export const enum Archtype {
Object,
Array,
Map,
Set
}
// src/types/types-internal.ts#L27
export const enum ProxyType {
ProxyObject,
ProxyArray,
Map,
Set,
ES5Object,
ES5Array
}
// src/utils/common.ts#L160
export function latest(state: ImmerState): any {
// 优先返回copy_,因为copy_保存的是base更改之后的拷贝
return state.copy_ || state.base_
}
// src/utils/common.ts#L118
export function has(thing: any, prop: PropertyKey): boolean {
return getArchtype(thing) === Archtype.Map
// 区分map
// 判断是否有这个属性
? thing.has(prop)
: Object.prototype.hasOwnProperty.call(thing, prop)
}
// src/utils/common.ts#L101
export function getArchtype(thing: any): Archtype {
/* istanbul ignore next */
// 如果只是一个plainObject,则state为undefined
const state: undefined | ImmerState = thing[DRAFT_STATE]
return state
? state.type_ > 3
? state.type_ - 4 // cause Object and Array map back from 4 and 5
: (state.type_ as any) // others are the same
: Array.isArray(thing)
? Archtype.Array
: isMap(thing)
? Archtype.Map
: isSet(thing)
? Archtype.Set
// 例如 如果原数据是对象,最终判断会落在这
: Archtype.Object
}
源码
export function createProxy<T extends Objectish>(
immer: Immer,
value: T,
parent?: ImmerState
): Drafted<T, ImmerState> {
// 前提条件:createProxy应该由isDraftable保护,所以我们知道我们可以安全地起草
// 判断对象的类型,使用不同的方法
const draft: Drafted = isMap(value)
? getPlugin("MapSet").proxyMap_(value, parent)
: isSet(value)
? getPlugin("MapSet").proxySet_(value, parent)
// 是否支持proxy
: immer.useProxies_
? createProxyProxy(value, parent)
: getPlugin("ES5").createES5Proxy_(value, parent)
const scope = parent ? parent.scope_ : getCurrentScope()
scope.drafts_.push(draft)
return draft
}
export function getCurrentScope() {
// 错误信息 0: "Illegal state"
if (__DEV__ && !currentScope) die(0)
return currentScope!
}
Object
Map
Set
processResult
src/core/finalize.ts#L20
重点,将更改之后的结果输出
export function processResult(result: any, scope: ImmerScope) {
scope.unfinalizedDrafts_ = scope.drafts_.length
// 基于base生成的proxy,会在scope.drafts_第一项
const baseDraft = scope.drafts_![0]
const isReplaced = result !== undefined && result !== baseDraft
if (!scope.immer_.useProxies_)
getPlugin("ES5").willFinalizeES5_(scope, result, isReplaced)
if (isReplaced) {
if (baseDraft[DRAFT_STATE].modified_) {
revokeScope(scope)
die(4)
}
if (isDraftable(result)) {
// Finalize the result in case it contains (or is) a subset of the draft.
result = finalize(scope, result)
if (!scope.parent_) maybeFreeze(scope, result)
}
if (scope.patches_) {
getPlugin("Patches").generateReplacementPatches_(
baseDraft[DRAFT_STATE].base_,
result,
scope.patches_,
scope.inversePatches_!
)
}
} else {
// 计算最终值
result = finalize(scope, baseDraft, [])
}
revokeScope(scope)
if (scope.patches_) {
scope.patchListener_!(scope.patches_, scope.inversePatches_!)
}
return result !== NOTHING ? result : undefined
}
finalize
src/core/finalize.ts#L57
function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
// 不要在递归数据结构中递归
// 判断value是否冻结,兼容ie (Fixes #600)
if (isFrozen(value)) return value
const state: ImmerState = value[DRAFT_STATE]
// 一个普通的对象,可能需要冻结,可能包含draft
if (!state) {
// 遍历,根据格式判断分为两种:
// Object.keys().forEach \ .forEach
each(
value,
(key, childValue) =>
finalizeProperty(rootScope, state, value, key, childValue, path),
true // See #590, 不要递归到不可枚举的非draft对象
)
return value
}
// 永远不要最终确定另一个范围拥有的drafts
if (state.scope_ !== rootScope) return value
// 未修改的,返回(冻结的)原值
if (!state.modified_) {
maybeFreeze(rootScope, state.base_, true)
return state.base_
}
// 还没有最终确定,现在就开始吧
if (!state.finalized_) {
state.finalized_ = true
state.scope_.unfinalizedDrafts_--
const result =
// For ES5, create a good copy from the draft first, with added keys and without deleted keys.
state.type_ === ProxyType.ES5Object || state.type_ === ProxyType.ES5Array
? (state.copy_ = shallowCopy(state.draft_))
: state.copy_
// Finalize all children of the copy
// For sets we clone before iterating, otherwise we can get in endless loop due to modifying during iteration, see #628
// Although the original test case doesn't seem valid anyway, so if this in the way we can turn the next line
// back to each(result, ....)
each(
state.type_ === ProxyType.Set ? new Set(result) : result,
(key, childValue) =>
finalizeProperty(rootScope, state, result, key, childValue, path)
)
// everything inside is frozen, we can freeze here
maybeFreeze(rootScope, result, false)
// first time finalizing, let's create those patches
if (path && rootScope.patches_) {
getPlugin("Patches").generatePatches_(
state,
path,
rootScope.patches_,
rootScope.inversePatches_!
)
}
}
return state.copy_
}
[TOC]
immer源码解析
入口
produce
例子
produce中对象的更改不会导致原值更改源码
接下来看源码,看着不长,但是引用的函数较多
isDraftable
isPlainObject:DRAFTABLE:isMap\isSet:enterScope
例1
createProxy
公共参数和函数
源码
Object
createProxyProxy(value, parent)Object:
objectTraps例1
Array:
Map
Set
processResult
finalize