我们知道父子组件可以通过props $emit来传递数据,那么父孙组件之间怎么传递数据呢?VUE提供了provide/inject 来实现此功能。

还是先看看provide/inject怎么用的。

<script src="./vue.js"></script>
    <div id="app">
        <child-comp></child-comp>
    </div>
<script>
    Vue.component('child-comp', {
        template: "<div><grand-child-comp></grand-child-comp></div>",
        inject: ["parentValue"],
        mounted: function () {
              console.log("child-comp,parent data is: " + this.parentValue);
        }
    });
    Vue.component('grand-child-comp', {
        template: "<div></div>",
        inject: ["parentValue"],
        mounted: function () {
              console.log("grand-child-comp,parent data is: " + this.parentValue);
        }
    });
 
    const app = new Vue({
        el: '#app',        
        provide: {
            parentValue: "here is parent data"
        }        
    });
</script>

父组件提供了provide对象,子组件child-comp 和 孙组件grand-child-comp inject 了这个对象。通过打压我们看到子组件和孙组件都能顺利的取到父组件的数据。那么整个过程是怎么样的呢?

还记得我们注册全局组件过程中所用到的Vue.extend方法吗,在这个方法里面有以下代码段:

    Sub.options = mergeOptions(
        Super.options,
        extendOptions
      );

extendOptions 就包含我们inject属性,看看mergeOptions函数:

function mergeOptions (
    parent,
    child,
    vm
  ) {
    {
      checkComponents(child);
    }

    if (typeof child === 'function') {
      child = child.options;
    }

    normalizeProps(child, vm);
    normalizeInject(child, vm);
    normalizeDirectives(child);

    // Apply extends and mixins on the child options,
    // but only if it is a raw options object that isn't
    // the result of another mergeOptions call.
    // Only merged options has the _base property.
    if (!child._base) {
      if (child.extends) {
        parent = mergeOptions(parent, child.extends, vm);
      }
      if (child.mixins) {
        for (var i = 0, l = child.mixins.length; i < l; i++) {
          parent = mergeOptions(parent, child.mixins[i], vm);
        }
      }
    }

    var options = {};
    var key;
    for (key in parent) {
      mergeField(key);
    }
    for (key in child) {
      if (!hasOwn(parent, key)) {
        mergeField(key);
      }
    }
    function mergeField (key) {
      var strat = strats[key] || defaultStrat;
      options[key] = strat(parent[key], child[key], vm, key);
    }
    return options
  }

在这个函数里面看到了normalizeInject(child, vm);  看看这个函数:

  /**
   * 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:["parentValue"] 挂在了子组件中的$options.inject对象下

vm.$options.inject = {"parentValue": {"from": "parentValue"}}

我们在实例化子组件的时候要用到。看看实例化子组件时对inject的处理。在_init时会调用initInjections函数,看看这个函数:

  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);
    }
  }

看看这个resolveInject函数:

  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
    }
  }

从这个函数可以看到通过while循环,以及source = source.$parent 找到父组件中的_provided属性,拿到其值,也就拿到父组件提供的provide了。所以说孙组件可以拿到父组件中的数据。接下来我们就看看这个_provided属性。

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

在initProvide中我们看到了对_provided,通过打印发现vm.$options.provide是个函数,其实是调用这个函数得到的_provided。那么就看看这个函数。在父组件实例化时,我们也调用了mergeOptions对父组件中的provide属性进行了处理:

     if (options && options._isComponent) {
        // optimize internal component instantiation
        // since dynamic options merging is pretty slow, and none of the
        // internal component options needs special treatment.
        initInternalComponent(vm, options);
      } else {
        vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
        );
      }

这个options参数里面就包含provide属性。看看mergeOptions函数里面怎么对provide进行处理的:

    for (key in parent) {
      mergeField(key);
    }
    for (key in child) {
      if (!hasOwn(parent, key)) {
        mergeField(key);
      }
    }
    function mergeField (key) {
      var strat = strats[key] || defaultStrat;
      options[key] = strat(parent[key], child[key], vm, key);
    }

我们看到了处理过程,看看strats,

strats.provide = mergeDataOrFn;

  function mergeDataOrFn (
    parentVal,
    childVal,
    vm
  ) {
    if (!vm) {
      // in a Vue.extend merge, both should be functions
      if (!childVal) {
        return parentVal
      }
      if (!parentVal) {
        return childVal
      }
      // when parentVal & childVal are both present,
      // we need to return a function that returns the
      // merged result of both functions... no need to
      // check if parentVal is a function here because
      // it has to be a function to pass previous merges.
      return function mergedDataFn () {
        return mergeData(
          typeof childVal === 'function' ? childVal.call(this, this) : childVal,
          typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
        )
      }
    } else {
      return function mergedInstanceDataFn () {
        // instance merge
        var instanceData = typeof childVal === 'function'
          ? childVal.call(vm, vm)
          : childVal;
        var defaultData = typeof parentVal === 'function'
          ? parentVal.call(vm, vm)
          : parentVal;
        if (instanceData) {
          return mergeData(instanceData, defaultData)
        } else {
          return defaultData
        }
      }
    }
  }

通过打印我们看到了vm.$options.provide 就是mergedInstanceDataFn函数。通过调用这个函数我们_provided就成为了{"parentValue":"here is parent data"}。

至此我们就对provide/inject 原理分析完了。

 

Logo

前往低代码交流专区

更多推荐