取代深克隆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
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM