immer 原理

2020-09-28  本文已影响0人  依然还是或者其他

前言

掘金上的immer源码解析,根据的是以前的源码,跟现在的源码略有不同,大体思路上是可以参考的。大家自己看的话,最好还是以github上的源码为准,毕竟文章中的源码不知道什么时候就过时了。

下面的涉及的源码是7.0.9版本拉取的。

Proxy

immer 原理涉及到ES6的特性:Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

如下是对读取属性拦截的示例:

var proxy = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

Proxy支持的拦截操作:

immmer就是利用的Proxy的特性实现的。

immer原理

主要理解的是pruduce这个api,大部分情况下使用这个api就已经可以达到实现不可变数据的要求了。

const obj2=produce(obj, draft => {
  draft.count++
})

obj 是个普通对象,immer通过proxy给obj生成了一份草稿draft对象,当你对draft进行操作时,都会被监听,对draft的修改会进入自定义的setter函数。
在setter函数中,它并不会修改原始对象的值,而是递归父级不断拷贝,最终返回新的顶层对象,并作为produce函数的值。

produce

produce 方式其实就是Immer类中的produce方法

    produce(base: any, recipe?: any, patchListener?: any) {
        // curried invocation
        // 若base为函数,则返回一个调用函数curriedProduce,
        // curriedProduce进行正常调用produce,即base为对象,recipe为函数
        if (typeof base === "function" && typeof recipe !== "function") {
            
            const defaultBase = recipe
            recipe = base

            const self = this
            return function curriedProduce(
                this: any,
                base = defaultBase,
                ...args: any[]
            ) {
                return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
            }
        }

        //下面是base 为对象,recipe为函数, produce的主流程
        //
        if (typeof recipe !== "function") die(6)
        if (patchListener !== undefined && typeof patchListener !== "function")
            die(7)

        let result

        // Only plain objects, arrays, and "immerable classes" are drafted.
        // 只有base 是对象 ,数组,才会生成草稿immerable classes 

        //判断base 是否能被草稿化
        if (isDraftable(base)) {

            //对currentScope进行初始化
            /**scope的数据格式:
             * {
                    drafts_: [],
                    parent_,
                    immer_,
                    // Whenever the modified draft contains a draft from another scope, we
                    // need to prevent auto-freezing so the unowned draft can be finalized.
                    canAutoFreeze_: true,
                    unfinalizedDrafts_: 0
                }
             */
            const scope = enterScope(this)

            // 获取基于base的代理对象,用于监听处理 后续recipe函数操作并生成不可变对象
            const proxy = createProxy(this, base, undefined)
            let hasError = true
            try {
                //执行函数,一般回调函数不返回数据,所以result一般为undefined
                result = recipe(proxy)
                hasError = false
            } finally {
                // finally instead of catch + rethrow better preserves original stack
                if (hasError) revokeScope(scope)
                else leaveScope(scope)
            }
            if (typeof Promise !== "undefined" && result instanceof Promise) {
                //对于Promise 类型的处理
                return result.then(
                    result => {
                        usePatchesInScope(scope, patchListener)
                        return processResult(result, scope)
                    },
                    error => {
                        revokeScope(scope)
                        throw error
                    }
                )
            }
            usePatchesInScope(scope, patchListener)

            //解析并返回结果数据
            return processResult(result, scope)
        } else if (!base || typeof base !== "object") {
            result = recipe(base)
            if (result === NOTHING) return undefined
            if (result === undefined) result = base
            if (this.autoFreeze_) freeze(result, true)
            return result
        } else die(21, base)
    }
    
    

enterScope

export function enterScope(immer: Immer) {
    //  createScope返回scope,并赋值给全局currentScope
    return (currentScope = createScope(currentScope, immer))
}

function createScope(
    parent_: ImmerScope | undefined,
    immer_: Immer
): ImmerScope {
    //返回 immerscope对象
    return {
        drafts_: [],
        parent_,
        immer_,
        // Whenever the modified draft contains a draft from another scope, we
        // need to prevent auto-freezing so the unowned draft can be finalized.
        // 只要修改后的draft包含来自其他作用域的draft,我们需要在最终draft确定前,防止其自动冻结。
        canAutoFreeze_: true,
        unfinalizedDrafts_: 0
    }
}

createProxy

export function createProxy<T extends Objectish>(
    immer: Immer,
    value: T,
    parent?: ImmerState
): Drafted<T, ImmerState> {
    // precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
    // 前提条件: createProxy 应该有 isDraftable函数判断保护,所以我们才能在草稿进行安全地操作。


    //下面是value ,即传入produce的base的数据类型各自对于的处理
    const draft: Drafted = isMap(value)
        ? getPlugin("MapSet").proxyMap_(value, parent)
        : isSet(value)
        ? getPlugin("MapSet").proxySet_(value, parent)
        : immer.useProxies_  //判断是否支持 ES6的Proxy
        ? createProxyProxy(value, parent)  //** 支持es6 Proxy,生成draft
        : getPlugin("ES5").createES5Proxy_(value, parent)  //不支持,采用自带的es5"Proxy"

    const scope = parent ? parent.scope_ : getCurrentScope()
    scope.drafts_.push(draft)
    return draft
}
export function createProxyProxy<T extends Objectish>(
    base: T,
    parent?: ImmerState
): Drafted<T, ProxyState> {
    const isArray = Array.isArray(base)
    
    //初始化 base 的状态,用于记录后续数据操作的一些变化
    const state: ProxyState = {
        type_: isArray ? ProxyTypeProxyArray : (ProxyTypeProxyObject as any),
        // Track which produce call this is associated with.
        scope_: parent ? parent.scope_ : getCurrentScope()!,
        // True for both shallow and deep changes.
        modified_: false,
        // Used during finalization.
        finalized_: false,
        // Track which properties have been assigned (true) or deleted (false).
        assigned_: {},
        // The parent draft state.
        parent_: parent,
        // The base state.
        base_: base,
        // The base proxy.
        draft_: null as any, // set below
        // The base copy with any updated values.
        copy_: null,
        // Called by the `produce` function.
        revoke_: null as any,
        isManual_: false
    }
    
    let target: T = state as any

    // 默认捕获器 是obj类型的,若base 是数组类型,则捕获器改为对应的数组类型
    let traps: ProxyHandler<object | Array<any>> = objectTraps
    if (isArray) {
        target = [state] as any
        traps = arrayTraps
    }
    
    //生产Proxy代理对象
    const {revoke, proxy} = Proxy.revocable(target, traps)
    state.draft_ = proxy as any
    state.revoke_ = revoke
    return proxy as any
}

processResult

// 解析结果,并返回copy数据 或者 base数据
export function processResult(result: any, scope: ImmerScope) {
    scope.unfinalizedDrafts_ = scope.drafts_.length
    //获取数据的代理对象
    const baseDraft = scope.drafts_![0]
    const isReplaced = result !== undefined && result !== baseDraft
    if (!scope.immer_.useProxies_)
        getPlugin("ES5").willFinalizeES5_(scope, result, isReplaced)
    if (isReplaced) {
        if (baseDraft[DRAFT_STATE].modified_) {
            revokeScope(scope)
            die(4)
        }
        if (isDraftable(result)) {
            // Finalize the result in case it contains (or is) a subset of the draft.
            result = finalize(scope, result)
            if (!scope.parent_) maybeFreeze(scope, result)
        }
        if (scope.patches_) {
            getPlugin("Patches").generateReplacementPatches_(
                baseDraft[DRAFT_STATE],
                result,
                scope.patches_,
                scope.inversePatches_!
            )
        }
    } else {
        // Finalize the base draft.
        // result结果一般为undefined,所以一般为直接调用finalize,这个函数对返回state.copy_
        result = finalize(scope, baseDraft, [])
    }
    revokeScope(scope)
    if (scope.patches_) {
        scope.patchListener_!(scope.patches_, scope.inversePatches_!)
    }
    return result !== NOTHING ? result : undefined
}

总结下

从源码大致可以看出,传入produce的数据value(value为被drafted,主要为引用类型),会通过proxy生成一个代理对象valueProxy和对应的state。
state是每个value对象对应的状态,用于记录数据是否发生变更,是否需要生成新的copy,是否需要进行替换等。
valueProxy可以理解为我们操作的draft,valueProxy通过监听get、set方法等,根据state来判断和处理数据。
最终返回我们处理后的不可变数据。

参考

1.ECMAScript 6 入门
2.immer官网文档
3.精读《Immer.js》源码——黄子毅
4.Immer 全解析——Sheepy

上一篇下一篇

猜你喜欢

热点阅读