// 以此例讲解 |
生成代理
将
obj
包装一下,转为draft
。draft
是一个Proxy
。步骤如下:const state = {
modified, // 是否被修改过
finalized, // 是否已经完成(所有setter执行完,且已生成最终的copy)
parent, // 父级对象
base, // 原始对象(也就是obj)
copy, // base的浅拷贝(Object.assign)
draft, // 自身的代理
drafts, // 属性的代理
scope, // draft链数组
}
const draft = new Proxy(state, {
// getter
get(state, prop) {
if (prop === DRAFT_STATE) {
return state;
}
var drafts = state.drafts;
if (!state.modified && has(drafts, prop)) {
return drafts[prop];
}
var value = source$1(state)[prop];
if (state.finalized || !isDraftable(value)) {
return value;
}
if (state.modified) {
if (value !== peek$1(state.base, prop)) {
return value;
}
drafts = state.copy;
}
// 生成当前prop的draft,放到父级的drafts里
return drafts[prop] = createProxy$1(value, state);
},
// setter
set(state, prop, value) {
if (!state.modified) {
var baseValue = peek$1(state.base, prop);
var isUnchanged = value ? is(baseValue, value) || value === state.drafts[prop] : is(baseValue, value) && prop in state.base;
if (isUnchanged) {
return true;
}
markChanged$1(state);
}
state.assigned[prop] = true;
state.copy[prop] = value;
return true;
}
})这个
draft
暴露给用户去取值和赋值时,会触发getter
和setter
函数。getter
主要作用:
懒初始化代理对象
当代理对象子属性被访问时,才会生成子属性的代理对象。
以开头的例子来说:
首先,
state
的draft
是在执行reducer
之前就生成的;当执行到
state.products
时,即触发getter
,此时才会去生成state.products
的draft
,并放入state
的drafts
里。根据内部状态(
modified
、finalized
)决定返回值还是代理对象。get
接收两个参数,第一个为state
,即创建Proxy
时传入的第一个参数(目标对象),第二个参数为prop
,即想要获取的属性名,具体逻辑如下:- 若
prop
为DRAFT_STATE
则直接返回state
对象(会在最后处理结果时用到); - 取
state
的drafts
属性。drafts
中保存了state.base
子对象的 proxy,譬如base = { key1: obj1, key2: obj2 }
,则drafts = { key1: proxyOfObj1, key2: proxyOfObj2 }
; - 若
state
尚未被修改并且drafts
中存在prop
对应的proxy
,则返回该proxy
; - 若
state.copy
存在,则取state.copy[prop]
,否则取state.base[prop]
,存于value
; - 若
state
已经结束计算了或者value
不能用来生成proxy
,则直接返回value
; - 若
state
已被标记修改- 若
value !== state.base[prop]
则直接返回value
; - 否则把
state.copy
赋值给drafts
(copy
里也包含了子对象的proxy
,具体会在set
部分细说);
- 若
- 若未提前返回则执行
createProxy(value, state)
生成以value
为base
、state
为parent
的子state
的proxy
,存到drafts
里并返回。
- 若
setter
当
draft
修改时,首先对base
值进行浅拷贝,保存到copy
,将被修改的属性值保存到copy
,同时将modified
置为true
。同时,为了保证整条链路的对象都是新对象,会根据
parent
来递归父级,不断浅拷贝(即markChanged
方法,此时会往父级的copy
里写入子级的draft
),以保证从叶子节点到根节点链路上的每个节点都是新对象。function markChanged$1(state) {
if (!state.modified) {
state.modified = true;
// 往父级的copy写入子级的draft(这样就可以在第4步的生成环节递归子级)
state.copy = assign(shallowCopy(state.base), state.drafts);
state.drafts = null;
// 递归父级
if (state.parent) {
markChanged$1(state.parent);
}
}
}以开头的例子来说:
上一步中
state.products
被执行时,触发的getter
函数生成了state.products
的draft
,放入了state
的drafts
里,同时也返回给了state.products
这个表达式,供后续操作使用;此时对
state.products.count
赋值value
,就是对state.products
返回的draft
赋值value
,此时会做两件事:- 把
count: value
键值对放入state.products
的draft
的copy
; - 递归父级
draft
,即state
的draft
,这时会把state.products
的draft
放入state
的draft
的copy
里(具体就是把state
的draft
的drafts
挪到copy
),以此来标明state
节点的更新。
- 把
生成
Immutable
对象(1) 如果
modified
为true
说明对象没有被修改,那么直接返回
base
属性。(2) 如果
modified
为false
说明对象被修改了,按理是返回
copy
属性。但是setter
过程是递归的,draft
的子对象也是draft
,此时copy
里还含有很多draft
,所以想拿到真正的copy
属性,必须对state
再进行递归(finalize
),拿到真正的copy
值。// 开始递归,对root执行finalizeTree
finalizeTree(root, rootPath, scope) {
const state = root[DRAFT_STATE]
if (state) {
if (!this.useProxies) {
state.finalizing = true
state.copy = shallowCopy(state.draft, true)
state.finalizing = false
}
root = state.copy
}
const needPatches = !!rootPath && !!scope.patches
const finalizeProperty = (prop, value, parent) => {
if (value === parent) {
throw Error("Immer forbids circular references")
}
const isDraftProp = !!state && parent === root
if (isDraft(value)) {
const path =
isDraftProp && needPatches && !state.assigned[prop]
? rootPath.concat(prop)
: null
// 递归finalize(内部会用root的子节点去执行finalizeTree)
value = this.finalize(value, path, scope)
if (isDraft(value)) {
scope.canAutoFreeze = false
}
if (Array.isArray(parent) || isEnumerable(parent, prop)) {
parent[prop] = value
} else {
// 赋值
Object.defineProperty(parent, prop, {value})
}
if (isDraftProp && value === state.base[prop]) return
}
else if (isDraftProp && is(value, state.base[prop])) {
return
}
// 检查没有冻结的对象,把它们都进行递归,防止再次被写入draft
else if (isDraftable(value) && !Object.isFrozen(value)) {
each(value, finalizeProperty)
}
if (isDraftProp && this.onAssign) {
this.onAssign(state, prop, value)
}
}
// 运行
each(root, finalizeProperty)
return root
}从对象最外层往里递归,嵌套执行
finalizeTree
、finalizeProperty
,把copy
中的draft
都替换成真正的值(即子节点)。revoke
停止根节点的代理。