Immer 源码浅析
Immer (opens in a new tab) 是一个非常好玩的库,我在等飞机的时候读了一下它的源码。这篇文章(笔记?)旨在于通过阅读源码分析 Immer 的原理 (opens in a new tab)。我仅考虑了最常简单的使用方式,mutate 的对象也只是 plain object 而已,你可以在阅读本文之后再去探究其他 topic。
produce
最 common 的 produce 执行流程:
ImmerScope.scope
- 在
base
创建 root proxy,根据 base 的数据类型选择正确的 trap- 对于 Map 和 Set 有对应的 proxy,对于 plain object 使用 object proxy,不支持 Proxy fallback 到 ES5 definePropertypl
result = recipe(proxy)
,proxy 的 trap 执行变更逻辑scope.leave
processResult
scope
1
代表一次 produce
调用,也即 produce
执行的 context 。
/** Each scope represents a `produce` call. */
export class ImmerScope {
static current?: ImmerScope
patches?: Patch[]
inversePatches?: Patch[]
canAutoFreeze: boolean
drafts: any[]
parent?: ImmerScope
patchListener?: PatchListener
immer: Immer
constructor(parent: ImmerScope | undefined, immer: Immer) {
this.drafts = []
this.parent = parent
this.immer = immer
// Whenever the modified draft contains a draft from another scope, we
// need to prevent auto-freezing so the unowned draft can be finalized.
this.canAutoFreeze = true
}
usePatches(patchListener?: PatchListener) {}
revoke() {}
leave() {}
static enter(immer: Immer) {}
}
createProxy
2 3
创建了一个数据结构 ProxyState
,里面保存了 base
,Proxy 的 target 是这个 state
而非 base
。
const state: ProxyState = {
type: isArray ? ProxyType.ProxyArray : (ProxyType.ProxyObject as any),
// Track which produce call this is associated with.
scope: parent ? parent.scope : ImmerScope.current!,
// True for both shallow and deep changes.
modified: false,
// Used during finalization.
finalized: false,
// Track which properties have been assigned (true) or deleted (false).
assigned: {},
// The parent draft state.
parent,
// The base state.
base,
// The base proxy.
draft: null as any, // set below
// Any property proxies. 当前对象的属性的 proxy
drafts: {},
// The base copy with any updated values.
copy: null,
// Called by the `produce` function.
revoke: null as any,
isManual: false
}
objectTraps
这里主要关心 set get delete 三种会改变属性值的操作。
const objectTraps: ProxyHandler<ProxyState> = {
get(state, prop) {
if (prop === DRAFT_STATE) return state
let { drafts } = state
// Check for existing draft in unmodified state.
// 如果当前对象未被需改(这也意味着属性没有被修改),而且属性已经有了 Proxy
// 就返回属性的 Proxy,它能够处理之后的操作
if (!state.modified && has(drafts, prop)) {
return drafts![prop as any]
}
// 否则就获取属性的最新值
const value = latest(state)[prop]
// 如果这个 produce 过程已进入后处理阶段,或者属性对应的值不可代理,就直接返回
if (state.finalized || !isDraftable(value)) {
return value
}
// Check for existing draft in modified state.
// 如果当前对象已经被修改过
if (state.modified) {
// Assigned values are never drafted. This catches any drafts we created, too.
// 如果最新值不等于初始值,那么就返回这个最新值
if (value !== peek(state.base, prop)) return value
// Store drafts on the copy (when one exists).
// @ts-ignore
drafts = state.copy
}
// 否则为属性创建 Proxy,设置到 drafts 上并返回该 Proxy
return (drafts![prop as any] = state.scope.immer.createProxy(value, state))
},
set(state, prop: string /* strictly not, but helps TS */, value) {
// 如果当前对象没有被修改过
if (!state.modified) {
// 获取初始值,检查值是否发生了变化
const baseValue = peek(state.base, prop)
// Optimize based on value's truthiness. Truthy values are guaranteed to
// never be undefined, so we can avoid the `in` operator. Lastly, truthy
// values may be drafts, but falsy values are never drafts.
const isUnchanged = value
? is(baseValue, value) || value === state.drafts![prop]
: is(baseValue, value) && prop in state.base
if (isUnchanged) return true
// 没有变化直接返回,有变化执行以下逻辑
prepareCopy(state) // 如果当前对象没有被拷贝过,制作一层的浅拷贝
markChanged(state) // 将当前对象标记为脏,要向上递归
}
// 标识次属性也赋值过
state.assigned[prop] = true
// @ts-ignore
// 将新值设置到 copy 对象上
state.copy![prop] = value
return true
},
deleteProperty(state, prop: string) {
// 这个和 set 差不多,简单
// The `undefined` check is a fast path for pre-existing keys.
if (peek(state.base, prop) !== undefined || prop in state.base) {
state.assigned[prop] = false
prepareCopy(state)
markChanged(state)
} else if (state.assigned[prop]) {
// if an originally not assigned property was deleted
delete state.assigned[prop]
}
// @ts-ignor
if (state.copy) delete state.copy[prop]
return true
}
}
processResult
export function processResult(immer: Immer, result: any, scope: ImmerScope) {
const baseDraft = scope.drafts![0] // 获取根 draft,也就是调用 produce 所生成的 draft
const isReplaced = result !== undefined && result !== baseDraft
immer.willFinalize(scope, result, isReplaced)
if (isReplaced) {
if (baseDraft[DRAFT_STATE].modified) {
scope.revoke()
throw new Error("An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.") // prettier-ignore
}
if (isDraftable(result)) {
// Finalize the result in case it contains (or is) a subset of the draft.
result = finalize(immer, result, scope)
maybeFreeze(immer, result)
}
// ... patches 相关逻辑
} else {
// Finalize the base draft.
// 从根 draft 开始整理 result,移除当中的 Proxy
result = finalize(immer, baseDraft, scope, [])
}
// ... patches 相关逻辑
return result !== NOTHING ? result : undefined
}
finalize
finalizeProperty
finalizeTree
三者递归调用。