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 相关文章推荐
JS网络游戏-(模拟城市webgame)提供的一些例子下载
Oct 14 Javascript
javascript ie6兼容position:fixed实现思路
Apr 01 Javascript
动态显示可输入的字数提示还可以输入的字数
Apr 01 Javascript
jquery比较简洁的软键盘特效实现方法
Mar 19 Javascript
Javascript刷新窗口方法小结
Oct 21 Javascript
酷! 不同风格页面布局幻灯片特效js实现
Feb 19 Javascript
jQuery Easyui使用(一)之可折叠面板的布局手风琴菜单
Aug 17 Javascript
PHP抓取HTTPS内容和错误处理的方法
Sep 30 Javascript
vue.js框架实现表单排序和分页效果
Aug 09 Javascript
浅谈Emergence.js 检测元素可见性的 js 插件
Nov 18 Javascript
详解微信小程序中组件通讯
Oct 30 Javascript
vue-cli系列之vue-cli-service整体架构浅析
Jan 14 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加密解密类实例分析
2015/04/20 PHP
PHP超低内存遍历目录文件和读取超大文件的方法
2019/05/01 PHP
onbeforeunload与onunload事件异同点总结
2013/06/24 Javascript
jquery easyui 对于开始时间小于结束时间的判断示例
2014/03/22 Javascript
jquery操作HTML5 的data-*的用法实例分享
2014/08/17 Javascript
js内置对象处理_打印学生成绩单的简单实现
2016/09/24 Javascript
微信小程序 网络请求(GET请求)详解
2016/11/16 Javascript
用JavaScript实现让浏览器停止载入页面的方法
2017/01/19 Javascript
基于Bootstrap漂亮简洁的CSS3价格表(附源码下载)
2017/02/28 Javascript
bootstrap IE8 兼容性处理
2017/03/22 Javascript
使用prop解决一个checkbox选中后再次选中失效的问题
2017/07/05 Javascript
JS实现前端缓存的方法
2017/09/21 Javascript
10 种最常见的 Javascript 错误(频率最高)
2018/02/08 Javascript
浅谈VUE监听窗口变化事件的问题
2018/02/24 Javascript
关于vue面试题汇总
2018/03/20 Javascript
图片文字识别(OCR)插件Ocrad.js教程
2018/11/26 Javascript
微信小程序如何利用getCurrentPages进行页面传值
2019/07/01 Javascript
React实现全选功能
2020/08/25 Javascript
[00:44]2016完美“圣”典 风云人物:Mikasa宣传片
2016/12/07 DOTA
python实现在windows下操作word的方法
2015/04/28 Python
详解Python迭代和迭代器
2016/03/28 Python
python实现逻辑回归的方法示例
2017/05/02 Python
python tools实现视频的每一帧提取并保存
2020/03/20 Python
带有css3动画效果的兼容多浏览器简单导航条示例
2014/01/26 HTML / CSS
html5 制作地图当前定位箭头的方法示例
2020/01/10 HTML / CSS
Engel & Bengel官网:婴儿推车、儿童房家具和婴儿设备
2019/12/28 全球购物
机械专业毕业生推荐信范文
2013/11/25 职场文书
大学生个人简历中的自我评价
2013/12/27 职场文书
经典导游欢迎词大全
2014/01/16 职场文书
关于环保的标语
2014/06/13 职场文书
小学教师师德师风演讲稿
2014/08/22 职场文书
毕业论文指导教师评语
2014/12/30 职场文书
教师节寄语2015
2015/03/23 职场文书
房产证明范本
2015/06/19 职场文书
2016年基层党支部书记公开承诺书
2016/03/25 职场文书
详解OpenCV曝光融合
2022/04/29 Python