Open 2zH opened 6 years ago
主体部分在函数执行开始的位置先把当前 scope 下的 proxies 存为 previousProxies,函数执行的最后还原回去。函数内主要调用了,createProxy 与 finalize 这两个函数,通过 createProxy 生成 DraftStateTree,然后放入 producer 当中执行,若返回结果不为原 draft state tree 并且不是 undefined,通过 finalize 函数把 DraftStateTree 处理为 NextStateTree 并返回,同时 revoke 所有 proxies 中储存的 proxy 对象。
主体思路为: PrevStateTree -> createProxy -> DraftStateTree -> finalize -> NextStateTree.
function produce(baseState, producer) {
// ...
return getUseProxies()
? produceProxy(baseState, producer)
: produceEs5(baseState, producer)
}
// https://github.com/mweststrate/immer/blob/master/src/common.js#L16
// https://github.com/mweststrate/immer/blob/master/src/common.js#L34
function getUserProxies () {
return typeof Proxy !== "undefined"
}
// produceEs5是作为不支持 Proxy API 的兼容方案,这里暂且不讨论
// https://github.com/mweststrate/immer/blob/master/src/proxy.js#L131
// 主体部分
function produceProxy(baseState, producer) {
const previousProxies = proxies
proxies = []
try {
// create proxy for root
const rootProxy = createProxy(undefined, baseState)
// execute the thunk
const returnValue = producer.call(rootProxy, rootProxy)
// and finalize the modified proxy
let result
// check whether the draft was modified and/or a value was returned
if (returnValue !== undefined && returnValue !== rootProxy) {
// something was returned, and it wasn't the proxy itself
if (rootProxy[PROXY_STATE].modified)
throw new Error(RETURNED_AND_MODIFIED_ERROR)
// See #117
// Should we just throw when returning a proxy which is not the root, but a subset of the original state?
// Looks like a wrongly modeled reducer
result = finalize(returnValue)
} else {
result = finalize(rootProxy)
}
// revoke all proxies
each(proxies, (_, p) => p.revoke())
return result
} finally {
proxies = previousProxies
}
}
createProxy:
/// https://github.com/mweststrate/immer/blob/master/src/proxy.js#L122
function createProxy(parentState, base) {
const state = createState(parentState, base)
const proxy = Array.isArray(base)
? Proxy.revocable([state], arrayTraps)
: Proxy.revocable(state, objectTraps)
proxies.push(proxy)
return proxy.proxy
}
createState:
function createState(parent, base) {
return {
modified: false, // 是否被修改过
finalized: false, // 是否修改已经全部完成
parent, // 是否存在父元素
base, // 本体
copy: undefined, // 本体的浅拷贝
proxies: {} // 存储每个 propertyKey 的代理对象
}
}
objectTraps, arrayTraps:
对应不同类型的 Proxy handler.
const objectTraps = { get, has(target, prop) { return prop in source(target) }, ownKeys(target) { return Reflect.ownKeys(source(target)) }, set, deleteProperty, getOwnPropertyDescriptor, defineProperty, setPrototypeOf() { throw new Error("Don't even try this...") } }
const arrayTraps = {} each(objectTraps, (key, fn) => { arrayTraps[key] = function() { arguments[0] = arguments[0][0] return fn.apply(this, arguments) } }) // 这里主要列出的是 get/set ,帮助理解,其他被复写的方法/函数可去源码处研究。 // https://github.com/mweststrate/immer/blob/master/src/proxy.js#L58 function get(state, prop) { if (prop === PROXY_STATE) return state if (state.modified) { const value = state.copy[prop] if (value === state.base[prop] && isProxyable(value)) // only create proxy if it is not yet a proxy, and not a new object // (new objects don't need proxying, they will be processed in finalize anyway) return (state.copy[prop] = createProxy(state, value)) return value } else { if (has(state.proxies, prop)) return state.proxies[prop] const value = state.base[prop] if (!isProxy(value) && isProxyable(value)) return (state.proxies[prop] = createProxy(state, value)) return value } }
function set(state, prop, value) { if (!state.modified) { if ( (prop in state.base && is(state.base[prop], value)) || (has(state.proxies, prop) && state.proxies[prop] === value) ) return true markChanged(state) } state.copy[prop] = value return true }
finalize:
``` JavaScript
// https://github.com/mweststrate/immer/blob/master/src/common.js#L77
function finalize(base) {
if (isProxy(base)) {
const state = base[PROXY_STATE]
if (state.modified === true) {
if (state.finalized === true) return state.copy
state.finalized = true
// 此处存在递归调用
return finalizeObject(
useProxies ? state.copy : (state.copy = shallowCopy(base)),
state
)
} else {
return state.base
}
}
// 此处存在递归调用
finalizeNonProxiedObject(base)
return base
}
function finalizeObject(copy, state) {
const base = state.base
each(copy, (prop, value) => {
if (value !== base[prop]) copy[prop] = finalize(value)
})
return freeze(copy)
}
function finalizeNonProxiedObject(parent) {
// If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original
// tree and it could contain proxies at arbitrarily places. Let's find and finalize them as well
if (!isProxyable(parent)) return
if (Object.isFrozen(parent)) return
each(parent, (i, child) => {
if (isProxy(child)) {
parent[i] = finalize(child)
} else finalizeNonProxiedObject(child)
})
// always freeze completely new data
freeze(parent)
}
To be continue...
参考文章:
To be continue...
参考文章:
Official repo: https://github.com/mweststrate/immer
介绍
轻量的 immutable 库,由 mobx 作者 mweststrate 开发。与 mobx 有相似的地方 (并非状态管理库),但更轻量,功能粒度更细。它用来帮助不可变 (immutable) 状态树进行数据迭代,源码的实现上使用 Proxy API 生成 Draft state tree, 通过
getter/setter
来捕获对状态树的修改行为,以更合理的性能损耗 (cost) 迭代出一个全新的状态树。默认支持 Curry ,可用于函数合并。
官方介绍: Create the next immutable state tree by simply modifying the current tree 通过简单的修改当前状态树来创建下一个不可变状态树。
Example:
Reducer Example:
React.setState example: