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 相关文章推荐
javascript 写类方式之四
Jul 05 Javascript
JS实现标签页效果(配合css)
Apr 03 Javascript
控制input输入框中提示信息的显示和隐藏的方法
Feb 12 Javascript
Javascript高级技巧分享
Feb 25 Javascript
js使用html()或text()方法获取设置p标签的显示的值
Aug 01 Javascript
浅谈Javascript中substr和substring的区别
Sep 30 Javascript
PHP抓取HTTPS内容和错误处理的方法
Sep 30 Javascript
浅谈JavaScript中的属性:如何遍历属性
Sep 14 Javascript
vue-cli脚手架引入图片的几种方法总结
Mar 13 Javascript
详解关于vue-area-linkage走过的坑
Jun 27 Javascript
Vue 之孙组件向爷组件通信的实现
Apr 23 Javascript
layer.open 获取不到表单信息的解决方法
Sep 26 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
PHP Smarty生成EXCEL文档的代码
2008/08/23 PHP
php fputcsv命令 写csv文件遇到的小问题(多维数组连接符)
2011/05/24 PHP
基于PHP+Ajax实现表单验证的详解
2013/06/25 PHP
php连接与操作PostgreSQL数据库的方法
2014/12/25 PHP
浅析PHP关键词替换的类(避免重复替换,保留与还原原始链接)
2015/09/22 PHP
PHP中的Session对象如何使用
2015/09/25 PHP
PHP实现递归目录的5种方法
2016/10/27 PHP
Laravel 5使用Laravel Excel实现Excel/CSV文件导入导出的功能详解
2017/10/11 PHP
使用PHPStorm+XDebug搭建单步调试环境
2017/11/19 PHP
通过MSXML2自动获取QQ个人头像及在线情况(给初学者)
2007/01/22 Javascript
小议javascript 设计模式 推荐
2009/10/28 Javascript
JavaScript版的TwoQueues缓存模型
2014/12/29 Javascript
JS实现FLASH幻灯片图片切换效果的方法
2015/03/04 Javascript
jQuery+AJAX实现无刷新下拉加载更多
2015/07/03 Javascript
基于javascript制作微博发布栏效果
2016/04/04 Javascript
原生的强大DOM选择器querySelector介绍
2016/12/21 Javascript
详解如何在 vue 项目里正确地引用 jquery 和 jquery-ui的插件
2017/06/01 jQuery
JS 设置Cookie 有效期 检测cookie
2017/06/15 Javascript
小程序实现留言板
2018/11/02 Javascript
微信小程序 拍照或从相册选取图片上传代码实例
2019/08/28 Javascript
React生命周期原理与用法踩坑笔记
2020/04/28 Javascript
JavaScript中常用的3种弹出提示框(alert、confirm、prompt)
2020/11/10 Javascript
python 删除大文件中的某一行(最有效率的方法)
2017/08/19 Python
python Matplotlib画图之调整字体大小的示例
2017/11/20 Python
基于循环神经网络(RNN)的古诗生成器
2018/03/26 Python
Python二维数组实现求出3*3矩阵对角线元素的和示例
2019/11/29 Python
Linux下升级安装python3.8并配置pip及yum的教程
2020/01/02 Python
通过实例解析Python文件操作实现步骤
2020/09/21 Python
利用HTML5的新特点实现图片文件异步上传
2014/05/29 HTML / CSS
客服服务心得体会
2013/12/30 职场文书
播音主持专业个人自我评价
2014/01/09 职场文书
副主任竞聘演讲稿
2014/08/18 职场文书
工作经历证明范本
2015/06/15 职场文书
历史名人教你十五个读书方法,赶快Get起来!
2019/07/18 职场文书
html form表单基础入门案例讲解
2021/07/15 HTML / CSS
SQL实战演练之网上商城数据库商品类别数据操作
2021/10/24 MySQL