美文网首页
immer 原理

immer 原理

作者: 依然还是或者其他 | 来源:发表于2020-09-28 23:17 被阅读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支持的拦截操作:

    • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。
    • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
    • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
    • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
    • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
    • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
    • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
    • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
    • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
    • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
    • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
    • apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
    • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。

    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 生成当前全局的scope,后续用于保存draft,可以调用getCurrentScope来获取全局scope,进入获取draft
    • 调用createProxy,获取基于base的代理,从而生成draft,实现监听和处理相关的数据操作。
    • 调用recipe,实现用户的数据操作,返回result
    • 调用processResult来返回最终修改完成的不可变数据

    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

    相关文章

      网友评论

          本文标题:immer 原理

          本文链接:https://www.haomeiwen.com/subject/jdqsuktx.html