zoukankan      html  css  js  c++  java
  • 取代深克隆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
    }
  • 相关阅读:
    C#基础系列——一场风花雪月的邂逅:接口和抽象类
    C#进阶系列——动态Lamada(二:优化)
    C#进阶系列——动态Lamada
    JS组件系列——Bootstrap Table 表格行拖拽(二:多行拖拽)
    JS组件系列——Bootstrap Table 表格行拖拽
    C#进阶系列——DDD领域驱动设计初探(七):Web层的搭建
    C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入
    C#进阶系列——DDD领域驱动设计初探(六):领域服务
    C#进阶系列——DDD领域驱动设计初探(五):AutoMapper使用
    C#进阶系列——DDD领域驱动设计初探(四):WCF搭建
  • 原文地址:https://www.cnblogs.com/amiezhang/p/12299022.html
Copyright © 2011-2022 走看看