取代深克隆cloneDeep的方法 --- immer
参考阅读:https://juejin.im/post/5c079f9b518825689f1b4e88
一、使用
官网:https://immerjs.github.io/immer/docs/introduction
import produce from 'immer' const nextData = produce(原始data, (data) => { // 尽情的修改data把 })
demo:
import produce from 'immer' let currentState = { a: [], p: { x: 1 } } let nextState = produce(currentState, (draftState) => { draftState.a.push(2); })
二、原理
看代码:
produce(base: any, recipe?: any, patchListener?: any) { // ...省略一些前置参数判断,就是参数传错了就报错 let result // 只有 plain objects、arrays 和 "immerable classes" 能被 drafted if (isDraftable(base)) { // 在 ImmerScope.current(static) 创建并保存当然作用域,以后用于存放draft const scope = ImmerScope.enter(this) // 创建 Proxy 实例供第二个参数(recipe)任意修改 const proxy = this.createProxy(base, undefined) let hasError = true try { // 在 recipe 里面,用户做可以任意修改 result = recipe(proxy) hasError = false } finally { // 如果出错,则销毁 proxy if (hasError) scope.revoke() else scope.leave() } if (typeof Promise !== "undefined" && result instanceof Promise) { return result.then( result => { scope.usePatches(patchListener) return processResult(this, result, scope) }, error => { scope.revoke() throw error } ) } // 挂载钩子(可以获得change的动作) scope.usePatches(patchListener) // 最后输出修改后的结果 return processResult(this, result, scope) } else { result = recipe(base) if (result === NOTHING) return undefined if (result === undefined) result = base maybeFreeze(this, result, true) return result } } createProxy<T extends Objectish>( value: T, parent?: ImmerState ): Drafted<T, ImmerState> { // draft 就是 proxy const draft: Drafted = isMap(value) ? proxyMap(value, parent) : isSet(value) ? proxySet(value, parent) : this.useProxies ? createProxy(value, parent) // 一般 plain object 会在这里处理 p : createES5Proxy(value, parent) // ImmerScope.current 在这里存放 draft const scope = parent ? parent.scope : ImmerScope.current! scope.drafts.push(draft) return draft } function createProxy<T extends Objectish>( base: T, parent?: ImmerState ): Drafted<T, ProxyState> { const isArray = Array.isArray(base) const state: ProxyState = { type: isArray ? ProxyType.ProxyArray : (ProxyType.ProxyObject as any), // 作用域 scope: parent ? parent.scope : ImmerScope.current!, // 是否被改动过 modified: false, finalized: false, assigned: {}, parent, // 传进来的原始数据对象 base, // 基于本身建立的proxy draft: null as any, // set below // Any property proxies. drafts: {}, // 被修改后,最终输出就存放在copy属性 copy: null, // 存放“销毁”的方法 revoke: null as any, isManual: false } let target: T = state as any // objectTraps 作为 Proxy 的处理器 let traps: ProxyHandler<object | Array<any>> = objectTraps if (isArray) { target = [state] as any traps = arrayTraps } // 跟 new Proxy 差不多,但是返回多一个 revoke,可以用来销毁 proxy,节省内存空间 // 基于target(就是state),来建立 proxy const {revoke, proxy} = Proxy.revocable(target, traps) state.draft = proxy as any state.revoke = revoke return proxy as any } const objectTraps: ProxyHandler<ProxyState> = { get(state, prop) { // 最后输出的时候,直接返回整个 state if (prop === DRAFT_STATE) return state let {drafts} = state if (!state.modified && has(drafts, prop)) { return drafts![prop as any] } const value = latest(state)[prop] if (state.finalized || !isDraftable(value)) { return value } if (state.modified) { if (value !== peek(state.base, prop)) return value // @ts-ignore 用于忽略ts报错 drafts = state.copy } // 这里和 Vue 响应式原理差不多,set 之前必定先触发 get,触发 get 的时候,把改属性值也变成 Proxy return (drafts![prop as any] = state.scope.immer.createProxy(value, state)) }, has(state, prop) { return prop in latest(state) }, ownKeys(state) { return Reflect.ownKeys(latest(state)) }, set(state, prop: string /* strictly not, but helps TS */, value) { if (!state.modified) { const baseValue = peek(state.base, prop) const isUnchanged = value ? is(baseValue, value) || value === state.drafts![prop] : is(baseValue, value) && prop in state.base if (isUnchanged) return true // 给 state.copy 浅复制一层 prepareCopy(state) markChanged(state) } state.assigned[prop] = true // 这里用 copy 属性记录修改 // @ts-ignore 用于忽略ts报错 state.copy[prop] = value return true }, deleteProperty(state, prop: string) { if (peek(state.base, prop) !== undefined || prop in state.base) { state.assigned[prop] = false prepareCopy(state) markChanged(state) } else if (state.assigned[prop]) { delete state.assigned[prop] } // 这里在 copy 属性记录修改 // @ts-ignore 用于忽略ts报错 if (state.copy) delete state.copy[prop] return true }, getOwnPropertyDescriptor(state, prop) { const owner = latest(state) const desc = Reflect.getOwnPropertyDescriptor(owner, prop) if (desc) { desc.writable = true desc.configurable = state.type !== ProxyType.ProxyArray || prop !== "length" } return desc }, defineProperty() { throw new Error("Object.defineProperty() cannot be used on an Immer draft") // prettier-ignore }, getPrototypeOf(state) { return Object.getPrototypeOf(state.base) }, setPrototypeOf() { throw new Error("Object.setPrototypeOf() cannot be used on an Immer draft") // prettier-ignore } } function processResult(immer: Immer, result: any, scope: ImmerScope) { // 拿到 draft(就是我们的proxy) const baseDraft = scope.drafts[0] // 根元素是否被换掉了 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) if (!scope.parent) maybeFreeze(immer, result) } if (scope.patches) { scope.patches.push({ op: "replace", path: [], value: result }) scope.inversePatches!.push({ op: "replace", path: [], value: baseDraft[DRAFT_STATE].base }) } } else { // plain object 的时候走这里 result = finalize(immer, baseDraft, scope, []) } scope.revoke() if (scope.patches) { scope.patchListener!(scope.patches, scope.inversePatches!) } return result !== NOTHING ? result : undefined } function finalize( immer: Immer, draft: Drafted, scope: ImmerScope, path?: PatchPath ) { const state = draft[DRAFT_STATE] if (!state) { if (Object.isFrozen(draft)) return draft return finalizeTree(immer, draft, scope) } if (state.scope !== scope) { return draft } if (!state.modified) { maybeFreeze(immer, state.base, true) return state.base } if (!state.finalized) { state.finalized = true finalizeTree(immer, state.draft, scope, path) if (immer.onDelete && state.type !== ProxyType.Set) { if (immer.useProxies) { const {assigned} = state each(assigned, (prop, exists) => { if (!exists) immer.onDelete!(state, prop as any) }) } else { const {base, copy} = state each(base, prop => { if (!has(copy, prop)) immer.onDelete!(state, prop as any) }) } } if (immer.onCopy) { immer.onCopy(state) } if (immer.autoFreeze && scope.canAutoFreeze) { freeze(state.copy, false) } if (path && scope.patches) { generatePatches(state, path, scope.patches, scope.inversePatches!) } } // 最后返回 proxy 的 copy 作为结果 return state.copy }