// 以此例讲解 |
生成代理
将
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停止根节点的代理。