聊聊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 行级解析读取XML文件(附源码)
Oct 12 Javascript
使用Mootools动态添加Css样式表代码,兼容各浏览器
Dec 12 Javascript
通过Javascript创建一个选择文件的对话框代码
Jun 16 Javascript
setTimeout自动触发一个js的方法
Jan 15 Javascript
Node.js与Sails ~项目结构与Mvc实现及日志机制
Oct 14 Javascript
jqGrid 学习笔记整理——进阶篇(一 )
Apr 17 Javascript
Bootstrap模块dropdown实现下拉框响应
May 22 Javascript
Node.js实现文件上传
Jul 05 Javascript
基于JavaScript实现在新的tab页打开url
Aug 04 Javascript
微信小程序ajax实现请求服务器数据及模版遍历数据功能示例
Dec 15 Javascript
layui select获取自定义属性方法
Aug 15 Javascript
使用React-Router实现前端路由鉴权的示例代码
Jul 26 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 无限极分类
2008/03/27 PHP
怎么在Windows系统中搭建php环境
2013/08/31 PHP
PHP中一些可以替代正则表达式函数的字符串操作函数
2014/11/17 PHP
php生成图片验证码-附五种验证码
2015/08/19 PHP
PHP利用递归函数实现无限级分类的方法
2019/03/22 PHP
Laravel中10个有用的用法小结
2019/05/06 PHP
JavaScript ( (__ = !$ + $)[+$] + ({} + $)[_/_] +({} + $)[_/_] )
2011/02/25 Javascript
JSON.stringify 语法实例讲解
2012/03/14 Javascript
Extjs4 消息框去掉关闭按钮(类似Ext.Msg.alert)
2013/04/02 Javascript
css配合jquery美化 select
2013/11/29 Javascript
对比分析json及XML
2014/11/28 Javascript
Javascript 高阶函数使用介绍
2015/06/15 Javascript
Jquery实现弹性滑块滑动选择数值插件
2015/08/08 Javascript
JavaScript实现九九乘法表的简单实例
2016/06/07 Javascript
jQuery EasyUI学习教程之datagrid点击列表头排序
2016/07/09 Javascript
easyui取消表单实时验证,提交时统一验证的简单实例
2016/11/07 Javascript
详解layui弹窗父子窗口之间传参数的方法
2018/01/16 Javascript
node打造微信个人号机器人的方法示例
2018/04/26 Javascript
vue-cli3添加模式配置多环境变量的方法
2019/06/05 Javascript
Js Snowflake(雪花算法)生成随机ID的实现方法
2020/08/26 Javascript
[00:10]神之谴戒
2019/03/06 DOTA
[15:20]DOTA2-DPC中国联赛 正赛 Elephant vs Aster 选手采访
2021/03/11 DOTA
pandas.dataframe按行索引表达式选取方法
2018/10/30 Python
python实现各种插值法(数值分析)
2019/07/30 Python
使用python实现kNN分类算法
2019/10/16 Python
基于pygame实现童年掌机打砖块游戏
2020/02/25 Python
python虚拟环境模块venv使用及示例
2020/03/04 Python
Python的控制结构之For、While、If循环问题
2020/06/30 Python
python3代码中实现加法重载的实例
2020/12/03 Python
Bergfreunde丹麦:登山装备网上零售商
2017/02/26 全球购物
Expedia印度:您的一站式在线旅游网站
2017/08/24 全球购物
泰坦健身器材:Titan Fitness
2018/02/13 全球购物
英国第一家领先的在线处方眼镜零售商:Glasses Direct
2018/02/23 全球购物
税务干部群众路线教育实践活动自我剖析材料
2014/09/21 职场文书
有限责任公司股东合作协议书
2014/12/02 职场文书
PHP连接MSSQL数据库案例,PHPWAMP多个PHP版本连接SQL Server数据库
2021/04/16 PHP