Vue2关于inject和provide源码解析

187 阅读3分钟

关于vue2的inject和provide官方是这么说的

1、provide和inject需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖(provide),不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

2、provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key(但是只在原生支持Symbol和 Reflect.ownKeys 的环境下可工作)

3、inject 选项应该返回一个数组或者对象

其中数组是字符串数组 如果是对象,对象的属性可以直接是本地绑定名,或者是一个对象,该对象可以有两个属性:

from:这个是provide提供的注入属性名
default:默认值

关于inject和provide源码: ​

function mergeOptions(parent, child, vm) {
    ...
    normalizeProps(child, vm);
    normalizeInject(child, vm);
    normalizeDirectives(child);
    ...
    return options
}
Vue.prototype._init = function(options) {
       if (options && options._isComponent) {
          ...
        } else {
            vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
        }
        ...
        callHook(vm, 'beforeCreate');
        initInjections(vm);
        // resolve injections before data/props
        initState(vm);
        initProvide(vm);
        // resolve provide after data/props
        callHook(vm, 'created');
        ...
        if (vm.$options.el) {
            vm.$mount(vm.$options.el);
        }
    }

可以看到初始化时,依赖注入初始化有三步:

  1. 规范inject,normalizeInject
  2. 初始化inject,initInjections
  3. 初始化provide**,initProvide

1、normalizeInject源码解析

/**
 * Normalize all injections into Object-based format
 */
function normalizeInject(options, vm) {
    var inject = options.inject;
    if (!inject) {
        return
    }
    var normalized = options.inject = {};
    if (Array.isArray(inject)) {
        for (var i = 0; i < inject.length; i++) {
            normalized[inject[i]] = {
                from: inject[i]
            };
        }
    } else if (isPlainObject(inject)) {
        for (var key in inject) {
            var val = inject[key];
            normalized[key] = isPlainObject(val) ? extend({
                from: key
            }, val) : {
                from: val
            };
        }
    } else {
        warn("Invalid value for option \"inject\": expected an Array or an Object, " + "but got " + (toRawType(inject)) + ".", vm);
    }
}

结论:

  • inject应该为数组或者对象
  • inject的每一个注入属性被规范化为对象,对象内有一个属性from,用于存储注入的属性来源

2、initInjections源码解析,重点在于resolveInject方法

function initInjections(vm) {
    var result = resolveInject(vm.$options.inject, vm);
    if (result) {
        toggleObserving(false);
        Object.keys(result).forEach(function(key) {
            /* istanbul ignore else */
            {
                defineReactive$$1(vm, key, result[key], function() {
                    warn("Avoid mutating an injected value directly since the changes will be " + "overwritten whenever the provided component re-renders. " + "injection being mutated: \"" + key + "\"", vm);
                });
            }
        });
        toggleObserving(true);
    }
}

function resolveInject(inject, vm) {
    if (inject) {
        // inject is :any because flow is not smart enough to figure out cached
        var result = Object.create(null);
        var keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);

        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            // #6574 in case the inject object is observed...
            if (key === '__ob__') {
                continue
            }
            var provideKey = inject[key].from;
            var source = vm;
            while (source) {
                if (source._provided && hasOwn(source._provided, provideKey)) {
                    result[key] = source._provided[provideKey];
                    break
                }
                source = source.$parent;
            }
            if (!source) {
                if ('default'in inject[key]) {
                    var provideDefault = inject[key].default;
                    result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault;
                } else {
                    warn(("Injection \"" + key + "\" not found"), vm);
                }
            }
        }
        return result
    }
}

结论:

1、使用Reflect.ownKeys获取inject属性,是因为inject属性有可能是一个Symbol

2、依赖注入的属性应该在祖先组件中提供,因为会到祖先中寻找。这段代码也解释了initInjections在initProvide之前执行,因为在本身组件依赖注入(provide并inject)是没有意义的,还不如定义data。

image.png

3、再次证明from属性就是真正的注入属性名

4、如果所有祖先都没有该属性,说明没有注入;那么再次获取default属性,也就是默认值;如果也没有默认值,就报错了

3、initProvide源码解析

function initProvide(vm) {
    var provide = vm.$options.provide;
    if (provide) {
        vm._provided = typeof provide === 'function' ? provide.call(vm) : provide;
    }
}

结论: 如果注入的是函数就直接执行,如果不是,就直接返回provide对象; 注意:函数必须返回一个对象。 ​

以上就是一个vue组件初始化依赖注入的过程,但是如果还有子组件,怎么办呢?接下来简单说一下子组件是如何初始化依赖注入的。 大家都知道,vue渲染页面有以下几个过程:

  1. 把vue template(模板)生成渲染函数;
  2. 再把渲染函数生成VNode(虚拟DOM)
  3. 最后把VNode转换成真实的DOM渲染出来。

而初始化子组件的操作就在3步骤里面。源码如下:

function createComponent(Ctor, data, context, children, tag) {
    if (isUndef(Ctor)) {
        return
    }
    var baseCtor = context.$options._base;

    // plain options object: turn it into a constructor
    if (isObject(Ctor)) {
        Ctor = baseCtor.extend(Ctor);
    }
    ...
    return vnode
}


 Vue.extend = function(extendOptions) {
        extendOptions = extendOptions || {};
        var Super = this;
        var SuperId = Super.cid;
        var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
        if (cachedCtors[SuperId]) {
            return cachedCtors[SuperId]
        }

        var name = extendOptions.name || Super.options.name;
        if (name) {
            validateComponentName(name);
        }

        var Sub = function VueComponent(options) {
            this._init(options);
        };
        Sub.prototype = Object.create(Super.prototype);
        Sub.prototype.constructor = Sub;
        Sub.cid = cid++;
        Sub.options = mergeOptions(Super.options, extendOptions);
        Sub['super'] = Super;

        // For props and computed properties, we define the proxy getters on
        // the Vue instances at extension time, on the extended prototype. This
        // avoids Object.defineProperty calls for each instance created.
        if (Sub.options.props) {
            initProps$1(Sub);
        }
        if (Sub.options.computed) {
            initComputed$1(Sub);
        }

        // allow further extension/mixin/plugin usage
        Sub.extend = Super.extend;
        Sub.mixin = Super.mixin;
        Sub.use = Super.use;

        // create asset registers, so extended classes
        // can have their private assets too.
        ASSET_TYPES.forEach(function(type) {
            Sub[type] = Super[type];
        });
        // enable recursive self-lookup
        if (name) {
            Sub.options.components[name] = Sub;
        }

        // keep a reference to the super options at extension time.
        // later at instantiation we can check if Super's options have
        // been updated.
        Sub.superOptions = Super.options;
        Sub.extendOptions = extendOptions;
        Sub.sealedOptions = extend({}, Sub.options);

        // cache constructor
        cachedCtors[SuperId] = Sub;
        return Sub
    }

Sub.prototype = Object.create(Super.prototype);

Sub.prototype.constructor = Sub;

Super代表Vue构造函数,这两句是构造子组件构造函数继承自Super,也就是Vue。 子组件初始化就会调用Sub里面的_init,也就是Vue里面的_init函数,该函数就有依赖注入的初始化,完毕。