Vue 2.0 中依赖注入 provide/inject组合实战

用法

--------------------------------------------------------------------------------

先来看看官网的介绍:

简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件得资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱。这个就是这对选项要干的事情

provide和inject需要配合使用,它们的含义如下:

provide        ;一个对象或返回一个对象的函数,该对象包含可注入起子孙的属性,可以使用ES6的Symbols作为key(只有原生支持Symbol才可以)
inject         ;一个字符串数组或一个对象
                 ;字符串数组    ;provide对象里哪些属性可用
                ;一个对象        ;key是本地的绑定名,value是provide里对应的对象名,也可以是一个对象,此时from属性是provide里对应的对象名,default属性是不存在时的默认值

来个实例就明显了:

<!DOCTYPE html>    <!--例1-->    
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
  <title>Document</title>
</head>
<body>
  <div id="app"><child></child></div>
  <script>
    Vue.component('child',{
      inject:['message'],
      template:'<p>{{message}}</p>'
    })
    new Vue({
      el:'#app',provide:{message:'Hello Vue!'}
    })
  </script>
</body>
</html>

输出:Hello Vue!,对应的DOM节点渲染为:

是不是感觉和props的传值差不多,我们在中间再嵌套一层组件就知道他的用处了,例如:

<!DOCTYPE html>   <!--例2-->    
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
  <div id="app"><test></test></div>
  <script>
    Vue.component('child',{
      inject:['message'],
      template:'<p>{{message}}</p>'
    })
    Vue.component('test',{
      template:`<div><child></child></div>`
    })
    new Vue({
      el:'#app',provide:{message:'Hello Vue!'}
    })
  </script>
</body>
</html>

输出:Hello Vue!,对应的DOM节点渲染为:

就是这个用处吧,多层嵌套时还是很方便的

源码分析

--------------------------------------------------------------------------------

provide/inject组合的源码分为三个部分,分别是组件注册、Vue实例化和组件实例化的过程,如下:

组件注册时

注册时会执行Vue.extend()(第4770行),内部会执行mergeOptions()合并一些属性,mergeOptions如下:

function mergeOptions (  //第1451行
 parent,
 child,
 vm
) {
 {
  checkComponents(child);
 }

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

 normalizeProps(child, vm);
 normalizeInject(child, vm);     //对inject进行一次规范化
 normalizeDirectives(child);
 var extendsFrom = child.extends;
 if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, 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定义如下:

function normalizeInject (options, vm) { //第1398行
 var inject = options.inject;
 if (!inject) { return }
 var normalized = options.inject = {};
 if (Array.isArray(inject)) {           //如果inject是一个数组
  for (var i = 0; i < inject.length; i++) {      //遍历inject
   normalized[inject[i]] = { from: inject[i] };    //保存到normalized里面,例如:{foo: {from: "foo"}}
  }
 } else if (isPlainObject(inject)) {        //如果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
  );
 }
}

对于例1来说,mergeOptions()之后inject等于:{message: {from: "message"}},如下:

Vue实例化时

执行_init()时会执行mergeOptions()进行数据的合并,对于provide的合并策略等于mergeDataOrFn()函数(和data的合并策略是一样的,定义在1321行),返回一个匿名函数(第1154行),如下:

function mergeDataOrFn (  //第1154行
 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
   }
  }
 }
}

然后返回到_init()之后会调用initProvide()初始化provide:

function initProvide (vm) {   //第3619行
 var provide = vm.$options.provide;        //尝试获取provide
 if (provide) {                  //如果provide存在,当它是函数时执行该返回,否则直接将provide保存到Vue实例的_provided属性上
  vm._provided = typeof provide === 'function'
   ? provide.call(vm)
   : provide;
 }
}

返回后_provided等于{message:"Hello Vue!"},如下

e]

组件实例化时

_init()时会执行initInjections(),经过了前面两步的处理,这里比较简单了,直接从父Vue或父Vue的父Vue获取对应的值即可,如下:

function initInjections (vm) {   //第2681行 初始化inject
 var result = resolveInject(vm.$options.inject, vm);     //遍历祖先节点,获取对应的inject,例如:比如:{foo: "bar"}
 if (result) {                        //如果获取了对应的值,则将它变成响应式
  toggleObserving(false);
  Object.keys(result).forEach(function (key) {
   /* istanbul ignore else */
   {
    defineReactive(vm, key, result[key], function () {   //将key编程响应式,这样就可以访问该元素了
     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) {      //第3649行 确定Inject inject:例如:{foo: {from: "foo"}} vm:当前组件的实例
 if (inject) {                          //如果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).filter(function (key) {          //如果有符号类型,调用Reflect.ownKeys()返回所有的key,再调用filter
    /* istanbul ignore next */
    return Object.getOwnPropertyDescriptor(inject, key).enumerable
   })
   : Object.keys(inject);                       //获取所有的key,此时keys就是个字符串数组,比如:["foo"]

  for (var i = 0; i < keys.length; i++) {                //这里遍历每个key
   var key = keys[i];
   var provideKey = inject[key].from;
   var source = vm;
   while (source) {
    if (source._provided && hasOwn(source._provided, provideKey)) {  //如果source存在_provided 且 含有provideKey这个属性
     result[key] = source._provided[provideKey];             //则将值保存到result[key]中
     break                                //并跳出while循环
    }
    source = source.$parent;                     //否则将source赋值给父Vue实例,直到找到对应的providekey为止
   }
   if (!source) {                           //如果最后source不存在,即没有从当前实例或祖先实例的_provide找到privideKey这个key
    if ('default' in inject[key]) {
     var provideDefault = inject[key].default;             //如果有定义defult,则使用默认值
     result[key] = typeof provideDefault === 'function'
      ? provideDefault.call(vm)
      : provideDefault;
    } else {
     warn(("Injection \"" + key + "\" not found"), vm);
    }
   }
  }
  return result                            //返回结果,比如:{foo: "bar"}
 }
}

注:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

总结

以上所述是小编给大家介绍的Vue 2.0 中依赖注入 provide/inject组合实战,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • vue中的inject学习教程

    最近看源码有一段对于整合参数的代码, normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) 想象里边的Inject很少用到,所以查了一下资料, 通常组件传参是有两种情况 父子组件进行传参,这时候通常利用props 非父子组件传参,这时候一般利用vuex 会有一种情况隔代组件传参,这时候可以利用props一层一层传递下去,但是代码就比较乱了 所以就有了 provide/inject 进行隔代组

  • vue中的provide/inject的学习使用

    前言 最近在看element-ui的源码,发现了一个这样的属性:inject.遂查看官网provider/inject provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量. 需要注意的是这里不论子组件有多深,只要调用了inject那么就可以注入provider中的数据.而不是局限于只能从当前父组件的prop属性来获取数据. 下面我们来验证下猜想: first:定义一个parent component <template

  • 详解Vue实战指南之依赖注入(provide/inject)

    案例 UI美眉说咱家的选项菜单太丑了,小哥哥能不能美化一下呀,洒家自然是说小意思啦~ 自定义一个select组件,so easy~ 简单粗暴型: <el-select v-model="favourite" :option="[]"></el-select> option作为数据进来就ok啦. 然后发现下列问题: key-value,不是所有的接口都是id-name option要disabled 怎么办? option存在几种情况怎么办?

  • 详解Vue 多级组件透传新方法provide/inject

    provide / inject 是 2.2 新增的方法,可以以一个祖先组件向所有子孙后代注入依赖(一个内容). provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量. Vue 官方警告: provide 和 inject 主要为高阶插件/组件库提供用例.并不推荐直接用于应用程序代码中. 当然,警告只是警告,你完全可以正常使用. 使用方法非常像 data 和 props 的组合大礼包: var Provider = {

  • Vue 2.0 中依赖注入 provide/inject组合实战

    用法 -------------------------------------------------------------------------------- 先来看看官网的介绍: 简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件得资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱.这个就是这对选项要干的事情 provide和inject需要配合使用,它们的含义如下: provide        ;一个对象或返回一个对象的函数,该对象包含可注入起子孙的属

  • 详析Spring中依赖注入的三种方式

    前言 平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类中.依赖注入的另一种说法是"控制反转",通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员,而控制反转是指new实例工作不由我们程序员来做而是交给spring容器来做. 在Sprin

  • Vue CLI3.0中使用jQuery和Bootstrap的方法

    Vue 中使用 jQuery 和 Bootstrap 不是特别符合 Vue 原生的写法,但是有时候又要用,所以放上我的引入设置,供大家参考. 在 Vue CLI2.0 中引入 jQuery 和 Bootstrap 需要设置很多配置项,网上有很多方法法,这里不重复写了.直接上 Vue CLI3.0 配置步骤. 第一步:安装 jQuery. Bootstrap.popper.js依赖. 其中popper.js 用于在 Bootstrap 中显示弹窗.提示.下拉菜单,所以需要引入. npm insta

  • Vue.js2.0中的变化小结

    最近自己在学习Vue.js,在看一些课程的时候可能Vue更新太块了导致课程所讲知识和现在Vue的版本不符,从而报错,我会在以后的帖子持续更新Vue的变化与更新,大家也可以一起交流,共同监督学习! 1.关于Vue中$index获取索引值已经取消,多用于多个元素的操作,像ul中的li,通过v-for来建立多个li,如果对于其中的某个或者一些li操作的话,需要使用到索引值,用法如下; <template> <div class="hello"> <h1>{

  • .NET Core中依赖注入AutoMapper的方法示例

    本文主要介绍了关于.NET Core中依赖注入AutoMapper的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 最近在 review 代码时发现同事没有像其他项目那样使用 AutoMapper.Mapper.Initialize() 静态方法配置映射,而是使用了依赖注入 IMapper 接口的方式 services.AddSingleton<IMapper>(new Mapper(new MapperConfiguration(cfg => { cfg.Cr

  • 解决angularjs service中依赖注入$scope报错的问题

    控制台错误提示 ionic.bundle.js:26794 Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- DutylogService http://errors.angularjs.org/1.5.3/$injector/unpr?p0=<ion-nav-view name="tab-dutylog" class="view-container tab-conte

  • Vue 3.0中jsx语法的使用

    Vue 3.0 正式发布了,喜大普奔

  • 深入浅出讲解Spring框架中依赖注入与控制反转及应用

    目录 一. 概念: 1. 使用前: 2. 使用后: 二. 理解控制反转(Ioc): 三. IoC的应用方法 一. 概念: 依赖注入(Dependency Injection,DI)与控制反转(IoC)的含义相同,只不过是从两个角度描述的同一个概念.对于一个Spring初学者来说,这两种称呼都很难理解,我们通过简单的语言来描述这两个概念. 使用对比: 1. 使用前: 当某个Java对象(调用者)需要调用另一个Java对象(被调用者,就是被依赖对象)时,在传统模式下,调用者通常会采用"new被调用者

  • Asp.net core中依赖注入的实现

    使用服务 在Asp.net core的Controller中,可以通过如下两种方式获取系统注入的服务: 构造函数 可以直接在构造函数中传入所依赖的服务,这是非常常见的DI注入方式.     public ValuesController(IConfiguration cfg)     {     } FromService参数 也可以直接在参数中通过FromServiceAttribute引入服务,这个在Controller中用起来非常方便,可以不用再构造函数中加一个变量以保存服务.     [

随机推荐