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


Posted in Javascript onJune 20, 2019

用法

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

先来看看官网的介绍:

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节点渲染为:

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

是不是感觉和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节点渲染为:

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

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

源码分析

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

  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 2.0 中依赖注入 provide/inject组合实战

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]Vue 2.0 中依赖注入 provide/inject组合实战

组件实例化时

 _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组合实战,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
把textarea中字符串里含有的回车换行替换成&amp;lt;br&amp;gt;的javascript代码
Apr 20 Javascript
jquery获取css中的选择器(实例讲解)
Dec 02 Javascript
浅谈Javascript 数组与字典
Jan 29 Javascript
js鼠标滑过图片震动特效的方法
Feb 17 Javascript
Javascript定义类(class)的三种方法详解
Mar 13 Javascript
js实现三张图(文)片一起切换的banner焦点图
Aug 25 Javascript
jQuery 全选 全不选 事件绑定的实现代码
Jan 23 Javascript
vue-cli 如何打包上线的方法示例
May 08 Javascript
vue两组件间值传递 $router.push实现方法
May 15 Javascript
Angular封装表单控件及思想总结
Dec 11 Javascript
vue项目中在可编辑div光标位置插入内容的实现代码
Jan 07 Javascript
Vue v-bind动态绑定class实例方法
Jan 15 Javascript
js实现页面多个日期时间倒计时效果
Jun 20 #Javascript
vue实现绑定事件的方法实例代码详解
Jun 20 #Javascript
javascript实现5秒倒计时并跳转功能
Jun 20 #Javascript
JS实现的简单tab切换功能完整示例
Jun 20 #Javascript
Vue 页面权限控制和登陆验证功能的实例代码
Jun 20 #Javascript
jQuery实现文本显示一段时间后隐藏的方法分析
Jun 20 #jQuery
javascript获取select值的方法完整实例
Jun 20 #Javascript
You might like
30 个很棒的PHP开源CMS内容管理系统小结
2011/10/14 PHP
php根据isbn书号查询amazon网站上的图书信息的示例
2014/02/13 PHP
ThinkPHP模板自定义标签使用方法
2014/06/26 PHP
php实现购物车功能(上)
2020/07/23 PHP
php的socket编程详解
2016/11/20 PHP
PHP与JavaScript针对Cookie的读写、交互操作方法详解
2017/08/07 PHP
PHP 使用二进制保存用户状态的实例
2018/01/29 PHP
php-7.3.6 编译安装过程
2020/02/11 PHP
javascript 循环读取JSON数据的代码
2010/07/17 Javascript
jQuery实现鼠标滑过遮罩并高亮显示效果
2013/07/16 Javascript
JavaScript ES6的新特性使用新方法定义Class
2016/06/28 Javascript
AngularJS基础 ng-submit 指令简单示例
2016/08/03 Javascript
React简单介绍
2017/05/24 Javascript
angularjs实现搜索的关键字在正文中高亮出来
2017/06/13 Javascript
JavaScript之Canvas_动力节点Java学院整理
2017/07/04 Javascript
详解Angular4 路由设置相关
2017/08/26 Javascript
深入浅析JavaScript中的RegExp对象
2017/09/18 Javascript
JS同步、异步、延迟加载的方法
2018/05/05 Javascript
详解小程序缓存插件(mrc)
2018/08/17 Javascript
JavaScript编码小技巧分享
2020/09/17 Javascript
python如何实现excel数据添加到mongodb
2015/07/30 Python
python实现按关键字筛选日志文件
2019/12/24 Python
深入浅析Python 函数注解与匿名函数
2020/02/24 Python
HTML5 b和i标记将被赋予真正的语义
2009/07/16 HTML / CSS
英国综合网上购物商城:The Hut
2018/07/03 全球购物
Hello Molly美国:女性时尚在线
2019/08/26 全球购物
N:Philanthropy官网:美国洛杉矶基础款服装
2020/06/09 全球购物
超市中秋节促销方案
2014/03/21 职场文书
让世界充满爱演讲稿
2014/05/24 职场文书
2014年群众路线教育实践活动整改措施
2014/09/24 职场文书
涉外离婚协议书怎么写
2014/11/20 职场文书
2014年节能减排工作总结
2014/12/06 职场文书
2015公务员试用期工作总结
2014/12/12 职场文书
ElementUI实现el-form表单重置功能按钮
2021/07/21 Javascript
一文弄懂MySQL索引创建原则
2022/02/28 MySQL
Mysql 数据库中的 redo log 和 binlog 写入策略
2022/04/26 MySQL