聊聊 Vue 中 provide/inject 的应用

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

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

何为 provide/inject

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

这对选项须要一块儿使用,以容许一个祖先组件向其全部子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。若是你熟悉 React,这与 React 的上下文特性很类似。学习

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

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

举个官网的🌰:翻译

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

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

能够看到,父组件提供的 foo 变量被子组件成功接收并使用。设计

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

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

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

那么,有没有方便快捷的实现全局状态的方法呢?固然有,这就是 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 有这么一个提示:

提示:provideinject 绑定并非可响应的。这是刻意为之的。然而,若是你传入了一个可监听的对象,那么其对象的属性仍是可响应的。

也就是说,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 作全局状态管理的原则:

  1. 多人协做时,作好做用域隔离
  2. 尽可能使用一次性数据做为全局状态

看起来,使用 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,也许你能发现一些不通常的风景,令你在解决一些问题时,事半功倍。

相关文章
相关标签/搜索