聊聊Vue中provide/inject的应用详解


Posted in Javascript onNovember 10, 2019

众所周知,在组件式开发中,最大的痛点就在于组件之间的通信。在 Vue 中,Vue 提供了各种各样的组件通信方式,从基础的 props/$emit 到用于兄弟组件通信的 EventBus,再到用于全局数据管理的 Vuex。

在这么多的组件通信方式中,provide/inject 显得十分阿卡林(毫无存在感)。但是,其实 provide/inject 也有它们的用武之地。今天,我们就来聊聊 Vue 中 provide/inject 的应用。

何为 provide/inject

provide/inject 是 Vue 在 2.2.0 版本新增的 API,官网介绍如下:

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

官网的解释很让人疑惑,那我翻译下这几句话:

provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。

举个官网的?:

// 父级组件提供 'foo'
var Provider = {
 provide: {
  foo: 'bar'
 },
 // ...
}

// 子组件注入 'foo'
var Child = {
 inject: ['foo'],
 created () {
  console.log(this.foo) // => "bar"
 }
 // ...
}

可以看到,父组件提供的 foo 变量被子组件成功接收并使用。

了解了 provide/inject 是什么后,我们再来使用使用 provide/inject。

使用 provide/inject 做全局状态管理

在日常开发中,我们经常会使用 Vuex 做状态管理,但是,我个人一直不喜欢使用 Vuex,原因在于 Vuex 为了保持状态可被回溯追踪,使用起来太过繁琐;而我之前参与的项目,较少多人合作,这个功能对于我来说,意义不大,我仅仅只需要 Vuex 中提供全局状态的功能。

那么,有没有方便快捷的实现全局状态的方法呢?当然有,这就是 provide/inject 这个黑科技 API 的一种使用方法。

很多人也许会想到一种方式:在根组件中,传入变量,然后在后代组件中使用即可。

// 根组件提供一个非响应式变量给后代组件
export default {
 provide () {
  return {
   text: 'bar'
  }
 }
}

// 后代组件注入 'app'
<template>
 <div>{{this.text}}</div>
</template>
<script>
 export default {
  inject: ['text'],
  created() {
   this.text = 'baz' // 在模板中,依然显示 'bar'
  }
 }
</script>

这个想法,说对也对,说不对也不对,原因在于 provide 的特殊性。

在官网文档中关于 provide/inject 有这么一个提示:

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

也就是说,Vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。

由于组件内部的各种状态就是可响应的,所以我们直接在根组件中将组件本身注入 provide,此时,我们可以在后代组件中任意访问根组件中的所有状态,根组件就成为了全局状态的容器,仔细想想,是不是很像 React 中的 context 呢?

代码如下:

// 根组件提供将自身提供给后代组件
export default {
 provide () {
  return {
   app: this
  }
 },
 data () {
  return {
   text: 'bar'
  }
 }
}

// 后代组件注入 'app'
<template>
 <div>{{this.app.text}}</div>
</template>
<script>
 export default {
  inject: ['app'],
  created() {
   this.app.text = 'baz' // 在模板中,显示 'baz'
  }
 }
</script>

也许有的同学会问:使用 $root 依然能够取到根节点,那么我们何必使用 provide/inject 呢?

在实际开发中,一个项目常常有多人开发,每个人有可能需要不同的全局变量,如果所有人的全局变量都统一定义在根组件,

势必会引起变量冲突等问题。
使用 provide/inject 不同模块的入口组件传给各自的后代组件可以完美的解决该问题。

慎用 provide/inject

既然 provide/inject 如此好用,那么,为什么 Vue 官方还要推荐我们使用 Vuex,而不是用原生的 API 呢?

我在前面提到过,Vuex 和 provide/inject 最大的区别在于,Vuex 中的全局状态的每次修改是可以追踪回溯的,而 provide/inject 中变量的修改是无法控制的,换句话说,你不知道是哪个组件修改了这个全局状态。

Vue 的设计理念借鉴了 React 中的单向数据流原则(虽然有 sync 这种破坏单向数据流的家伙),而 provide/inject 明显破坏了单向数据流原则。试想,如果有多个后代组件同时依赖于一个祖先组件提供的状态,那么只要有一个组件修改了该状态,那么所有组件都会受到影响。这一方面增加了耦合度,另一方面,使得数据变化不可控。如果在多人协作开发中,这将成为一个噩梦。

在这里,我总结了两条条使用 provide/inject 做全局状态管理的原则:

  • 多人协作时,做好作用域隔离
  • 尽量使用一次性数据作为全局状态

看起来,使用 provide/inject 做全局状态管理好像很危险,那么有没有 provide/inject 更好的使用方式呢?当然有,那就是使用 provide/inject 编写组件。

使用 provide/inject 编写组件

使用 provide/inject 做组件开发,是 Vue 官方文档中提倡的一种做法。

以我比较熟悉的 elementUI 来举例:

在 elementUI 中有 Button(按钮)组件,当在 Form(表单)组件中使用时,它的尺寸会同时受到外层的 FormItem 组件以及更外层的 Form 组件中的 size 属性的影响。

如果是常规方案,我们可以通过 props 从 Form 开始,一层层往下传递属性值。看起来只需要传递传递两层即可,还可以接受。但是,Form 的下一层组件不一定是 FormItem,FormItem 的下一层组件不一定是 Button,它们之间还可以嵌套其他组件,也就是说,层级关系不确定。如果使用 props,我们写的组件会出现强耦合的情况。

provide/inject 可以完美的解决这个问题,只需要向后代注入组件本身(上下文),后代组件中可以无视层级任意访问祖先组件中的状态。

部分源码如下:

// Button 组件核心源码
export default {
  name: 'ElButton',
  // 通过 inject 获取 elForm 以及 elFormItem 这两个组件
  inject: {
    elForm: {
      default: ''
    },
    elFormItem: {
      default: ''
    }
  },
  // ...
  computed: {
    _elFormItemSize() {
      return (this.elFormItem || {}).elFormItemSize;
    },
    buttonSize() {
      return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
    },
    //...
  },
  // ...
};

总结

其实在 Vue 的学习中,遵循着二八法则,我们常用的 20% 的 API 就能解决大部分日常问题,剩余的 API 感觉用处不大。但是,抽点时间去了解那些冷门的 API,也许你能发现一些不一般的风景,令你在解决一些问题时,事半功倍。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 处理网页内容的实现代码
Feb 15 Javascript
javascript实现checkBox的全选,反选与赋值
Mar 12 Javascript
jQuery Tags Input Plugin(添加/删除标签插件)详解
Jun 20 Javascript
js控制li的隐藏和显示实例代码
Oct 15 Javascript
详解javascript事件绑定使用方法
Oct 20 Javascript
火狐和ie下获取javascript 获取event的方法(推荐)
Nov 26 Javascript
JavaScript创建对象的七种方式(推荐)
Jun 26 Javascript
JS 实现百度搜索功能
Feb 01 Javascript
微信小程序日期选择器实例代码
Jul 18 Javascript
javacript replace 正则取字符串中的值并替换【推荐】
Sep 13 Javascript
Vue CLI 3.x 自动部署项目至服务器的方法
Apr 02 Javascript
Vue数据双向绑定原理实例解析
May 15 Javascript
浅析vue中的provide / inject 有什么用处
Nov 10 #Javascript
Vue项目中使用jsonp抓取跨域数据的方法
Nov 10 #Javascript
webpack4 optimization使用总结
Nov 10 #Javascript
vue ssr服务端渲染(小白解惑)
Nov 10 #Javascript
node后端服务保活的实现
Nov 10 #Javascript
vue动态循环出的多个select出现过的变为disabled(实例代码)
Nov 10 #Javascript
vue父子组件的通信方法(实例详解)
Nov 10 #Javascript
You might like
PHP初学者头疼问题总结
2006/10/09 PHP
解决文件名解压后乱码的问题 将文件名进行转码的代码
2012/01/10 PHP
php中3种方法统计字符串中每种字符的个数并排序
2012/08/27 PHP
Smarty高级应用之缓存操作技巧分析
2016/05/14 PHP
laravel框架关于搜索功能的实现
2018/03/15 PHP
laravel validate 设置为中文的例子(验证提示为中文)
2019/09/29 PHP
js Flash插入函数免激活代码
2009/03/31 Javascript
jquery的ajax从纯真网(cz88.net)获取IP地址对应地区名
2009/12/02 Javascript
让新消息在网页标题闪烁提示的jQuery代码
2013/11/04 Javascript
jquery实现弹出层完美居中效果
2014/03/03 Javascript
node.js中的fs.readdir方法使用说明
2014/12/17 Javascript
轻松学习jQuery插件EasyUI EasyUI实现拖动基本操作
2015/11/30 Javascript
JavaScript实现多种排序算法
2016/02/24 Javascript
深入理解JQuery循环绑定事件
2016/06/02 Javascript
Angular.js与node.js项目里用cookie校验账户登录详解
2017/02/22 Javascript
JS排序算法之希尔排序与快速排序实现方法
2017/12/12 Javascript
微信小程序表单验证form提交错误提示效果
2020/06/19 Javascript
vue 父组件中调用子组件函数的方法
2019/06/06 Javascript
跟老齐学Python之list和str比较
2014/09/20 Python
python实现用于测试网站访问速率的方法
2015/05/26 Python
使用C#配合ArcGIS Engine进行地理信息系统开发
2016/02/19 Python
对python使用http、https代理的实例讲解
2018/05/07 Python
用Pelican搭建一个极简静态博客系统过程解析
2019/08/22 Python
导致python中import错误的原因是什么
2020/07/01 Python
在pycharm中文件取消用 pytest模式打开的操作
2020/09/01 Python
python用Configobj模块读取配置文件
2020/09/26 Python
Python爬虫模拟登陆哔哩哔哩(bilibili)并突破点选验证码功能
2020/12/21 Python
英国独特礼物想法和个性化礼物网站:notonthehighstreet.com
2018/04/16 全球购物
eDreams德国:南欧领先的在线旅游公司
2020/12/07 全球购物
统计员岗位职责
2013/11/14 职场文书
优秀交警事迹材料
2014/01/26 职场文书
大学生党员学习焦裕禄精神思想汇报
2014/09/10 职场文书
导游词之江西赣州
2019/10/15 职场文书
22句经典语录:送给优柔寡断和胡思乱想的朋友们
2019/12/13 职场文书
解决mysql:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO/YES)
2021/06/26 MySQL
mysql通过group by分组取最大时间对应数据的两种有效方法
2022/09/23 MySQL