【vue】— provide/inject的原理

系列文章目录

【VUE】— diff算法原理

【VUE】— watch侦听器原理

一、provide/inject的作用及使用方式

依赖注入的主要只用是组件之间的传值。那相对于其他方式的特点:
优点:

  1. 祖先组件不需要知道哪些后代组件使用它提供的属性;
  2. 后代组件不需要知道被注入的属性来自哪里;

缺点:

  1. 组件间的耦合较为紧密,不易重构;
  2. 提供的属性是非响应式的;

使用:
父组件中提供依赖:

 provide("intentId", 'er34dfbsdfrf);

在子组件中注入:

const intentId = inject("intentId");

二、顺道复习一下组件间的通讯方式吧

  1. 父子组件传值:props
  2. 子父传值:触发自定义事件
  3. 兄弟组件传值: eventBus
  4. $parent / $childrenref
    • $parent方法是在子组件中可以直接访问该组件的父实例或组件。
    • $children方法是在父组件中可以直接访问子组件的实例,但是不保证子组件的顺序
    • ref被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例。
  5. vuex

三、源码分析

在这里插入图片描述

组件实例初始化的时候会调用Vue.prototype._initvm._init中在data/props前面调用了initInjections,在data/props后面调用了initProvide

3.1 initInjections

  function initInjections (vm) {
    
    
  // 根据注册的inject,通过$parent向上查找对应的provide
    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);
    }
  }

该方法主要做了以下两件事:

  1. 获取vm.$options.inject,通过resolveInject方法找到对应的key集合;
  2. 遍历key集合,对其进行响应式监听;

3.2 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);
 	// 循环向上遍历,直到拿到祖先节点的provide值
      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属性。

3.4 initProvide

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

该方法单纯把组件注册的provide值,赋值给vm._providedresolveInject中有使用到。
通过打印发现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"}

猜你喜欢

转载自blog.csdn.net/weixin_44761091/article/details/124150588