vue|vue @input带参数_Vue组件通信详解

使用Vue进行项目开发,碰到的比较多的问题就是如何进行组件之间的参数传递。为了能够更优雅的解决此类问题,笔者在这里总结了开发中经常用到的一些组件通信方式,并配合一些例子方便理解。

vue|vue @input带参数_Vue组件通信详解
文章图片

  • 源码地址:vue-component-communication
  • 在线访问
每一小节的例子都在src/views目录中,小伙伴可以结合对应的代码来阅读文章。
码字不易,如果文章内容对你有帮助的话,希望能点赞鼓励一下作者。
注:有些例子刻意为之,只是为了学习对应的知识点,对于实际使用场景刻意不必深究
props传参
Vue中,我们可以通过为子组件传入props,然后在子组件中接收,并通过this来直接访问

这里我们为demo-children传入了countadd-count属性,然后又将add-count传入到demo-grandson组件中。这样当我们分别点击父组件(demo-props)、子组件(demo-child)和孙子组件(demo-grandson)中的按钮时,都会更新count属性
当然我们也可以使用v-bind来直接绑定一个对象,Vue会帮我们将组件属性进行分发,类似于react中的{...props}

vue|vue @input带参数_Vue组件通信详解
文章图片
这张图的出处在这里,有兴趣的小伙伴可以去围观 : https:// twitter.com/tannerlinsl ey/status/1300847251846176768
Vue中我们也可以利用这个对象简写的特性来少敲几下键盘:

自定义事件
Vue中可以通过@符号来监听自定义事件,并在子组件中通过$emit方法来触发监听的事件。我们将上面的例子用自定义事件来进行改写:

完成上述代码后,我们依旧可以通过点击各个组件内的按钮来更新count属性
双向绑定v-model/.sync
Vue为了方便用户,提供了俩个可以实现双向绑定数据的语法糖。用户不再需要在父组件进行事件监听,便可以实现属性的自动更新。

相比于之前的传参方式,我们不再需要在父组件中监听addCount事件来更新父组件中的countVue会帮我们自动监听对应的事件,并更新属性值。
这俩个语法糖的本质如下:
  • v-model: 自动绑定value属性 + input事件
  • xxx.sync: 自动绑定update:xxx事件
下面我们模拟实现下这俩个语法为我们简化的一些事情:

上例中的count属性,我们通过value来接收,并将其传到子组件。然后子组件中通过调用this.$emit('input',this.value+1)通知父组件调用@input指令监听的事件,并将最新值作为参数传入。
父组件收到通知后调用@input指令监听的事件,并通过传入的参数来更新count属性。
而对于使用.sync修饰符的count1,我们可以随意指定其要传递给子组件的属性名,而不只能是value(v-model中的value也可以更改),并且会通过监听@update:count1,在count1发生变化后通过调用@update:count1对应的内容来更新count1。(注意:这里@update:count1中的count1与子组件中props接收的属性相同)
当然,v-model也并不是一定只能监听value属性和input事件,Vue为我们提供了自定义属性及更新事件的功能: f="https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model">自定义组件的v-model

vue|vue @input带参数_Vue组件通信详解
文章图片
到这里,我们使用v-model/.sync更简单的实现了功能。
$parent/$children
Vue可以让我们通过$parent/$children来直接访问到父组件或子组件实例,这样就可以直接使用组件实例中的任意属性和方法。

在父组件挂载完成后,通过this.$chilren[0]获取到了子组件实例,之后直接通过子组件实例来访问子组件的count属性。

在子组件中,也可以通过this.$parent来直接获取到父组件的count属性进行更新。
$attrs/$listeners
在很多情况下,我们并不需要重新封装一个组件,而是只需要在旧有组件的基础上再添加一些功能。这里我们就用到了$attrs$listenners属性,而$attrs又会与inheritAttrs属性一起使用。
先看一下这些属性的用途:
  • $attrs: 包含父作用域中绑定的没有被识别或提取为props的属性(classstyle除外)
  • inheritAttrs: 默认的,父作用域中没有被作为props识别的属性将会"回退",并且作为正常的HTML属性应用到子组件的根元素。设置inheritAttrsfalse,将会禁用这个默认行为。
  • $listenners: 包含父作用域中v-on绑定的监听器(不包括.native修饰符绑定的监听器)
假设我们有demo-grandson组件,可以接收count进行展示,并且接受addCount方法来更新count
而现在我们想要在不改变demo-grandson的基础上,再实现一个组件,它具有demo-grandson的所有功能,并且还可以展示标题。代码如下:

子组件中的$attrs为除title外的所有根元素中传入的属性组成的对象,配合inheritAttrs: false,并不会让其作为正常的HTML属性在element中展示。之后再配合v-bind将属性分发到demo-grandson上:

vue|vue @input带参数_Vue组件通信详解
文章图片
$listeners中包含v-on(即@)中绑定的所有事件监听函数,同理通过v-on分发到demo-grandson上:

vue|vue @input带参数_Vue组件通信详解
文章图片
这样demo-grandson中有再多的属性和事件,我们都可以通过v-bind=$attrsv-on=$linstenners进行传入。而不用每次都在props中定义,然后再单独在子组件标签上通过:@来进行绑定。
这俩个属性在对项目中用到的ui组件库进行二次封装时尤其好用,既可以保证使用原有组件所有的api,还可以额外封装一些项目中复用度高的功能。
依赖注入(provide/inject)
provide/inject通常用于跨层级传参,不管组件的层级有多深,都可以通过inject来获得父组件provide提供的内容。
provide/inject的数据传递思路如下:

vue|vue @input带参数_Vue组件通信详解
文章图片
通常情况下,我们会将父组件的实例通过provide传入,这样子组件通过inject就可以直接获取到父组件的实例,从而可以使用到父组件实例中定义的任意属性和方法,我们把之前的例子通过provide/inject来进行实现:

在子组件中调用addCount方法

在孙子组件中渲染count到页面中,并且通过按钮来更新count:

事件分发(dispatch)和广播(broadcast)
当我们的组件层级比较深的时候,我们需要一层一层向下传递事件,而当更新父组件中的某个属性时,又需要一层一层的将更新向上通知,大概的逻辑如下:

vue|vue @input带参数_Vue组件通信详解
文章图片
为了可以直接通过子组件更新父组件,而不再用经历中间的事件监听步骤,我们可以递归遍历找到父组件的子组件(demo-child),然后调用它的$emit('event-name')来更新父组件中的属性。这便是$dispatch方法的核心思路,代码如下:
Vue.prototype.$dispatch = function (ComponentName, event, ...args) { let parent = this.$parent; while (parent) { // const { name } = parent.$options; // 递归查找父组件,如果组件名满足要求的话,调用组件实例的 $emit方法 if (name === ComponentName) { parent.$emit(event, ...args); break; } parent = parent.$parent; } };

$broadcast方法可以帮我们在父组件中直接调用较深层的子组件的$emit('eventName')方法,从而通过子组件的父组件更改传入到子组件的值(在本例中为grandson传入到great-grandson中的name属性),代码如下:
Vue.prototype.$broadcast = function (ComponentName, event, ...args) { for (let i = 0; i < this.$children.length; i++) { const child = this.$children[i]; const { name } = child.$options; // 如果找到满足的子组件,调用 $emit方法 if (name === ComponentName) { child.$emit(event, ...args); } else { if (child.$children) { // 继续递归查找符合要求的子组件 child.$broadcast(ComponentName, event, ...args); } } } };

在原型上添加了对应的方法后,我们便可以在组件中通过组件实例来直接调用:

上边代码的逻辑大概如下:

vue|vue @input带参数_Vue组件通信详解
文章图片
现在我们便可以通过$dispatch/$broadcast来实现跨层级调用$emit方法,少写一些进行事件监听的@$emit代码。
上述代码参考element ui源码中$dispatch/$broadcast的相应实现:

vue|vue @input带参数_Vue组件通信详解
文章图片
elememnt ui并没有将方法挂载到Vue的原型上,而是定义了mixins中,最终可以通过mixins属性来混入到组件中进行使用:

vue|vue @input带参数_Vue组件通信详解
文章图片
截图中的代码地址,有兴趣的小伙伴可以点击链接直接查看:
  • emitter
  • form-item
事件总线(bus)
Vue通过$emit/$on实现了事件的发布订阅机制,通过$on来订阅事件,通过$emit来触发$on订阅的事件,并将需要的参数传入。我们也可以借助Vue$emit$on属性,来进行组件之间的函数调用以及参数传递。
首先我们需要在Vue的原型上扩展$bus属性,方便直接在组件中通过this.$bus来进行调用:
Vue.prototype.$bus = new Vue();

$bus的值是一个新的Vue实例,所以它可以调用Vue实例的$emit$on方法。
在父组件挂载完毕后,我们通过$bus.$on来订阅事件:

在子组件和孙子组件中,可以通过$bus.$emit来通知执行对应的订阅事件来更新count属性:

不管组件层级有多深,我们都可以通过约定好的名字(例子中是add-count)来直接调用父组件中的订阅函数。
Vuex
对于稍大规模一点的项目来说,通过Vuex来管理全局状态是比较好的选择。我们可以在任意组件使用Vuex中的state,并且可以通过commit一个mutation来更新状态。
下面我们用Vuex来再次实现count累加的例子。
首先在store中定义statemutation
export default new Vuex.Store({ state: { count: 0 }, mutations: { addCount (state, count) { state.count++; } }, });

可以在任意组件中引入,并且更改state
每个文件中引入Vuex辅助函数的代码如下:
import { mapMutations, mapState } from 'vuex'; export default { // ... some code computed: { ...mapState(['count']) }, methods: { ...mapMutations(['addCount']) } };

HTML模板代码:

现在我们可以通过Vuex辅助函数在实例上提供的count属性和addCount方法,就可以在任意组件使用和更新count
总结
当我们能够熟练掌握组件之间的各种传参技巧后,在实际的工作以及面试中便能够更加的游刃有余、从容不迫。
【vue|vue @input带参数_Vue组件通信详解】我们面临一个实际问题之后,所思考的不再是如何进行组件传参,而是如何能更好的选择和设计一种比较灵活优雅的传参方法,提高程序的可读性和可扩展性。

    推荐阅读