使用Vue
进行项目开发,碰到的比较多的问题就是如何进行组件之间的参数传递。为了能够更优雅的解决此类问题,笔者在这里总结了开发中经常用到的一些组件通信方式,并配合一些例子方便理解。
文章图片
- 源码地址:
vue-component-communication
- 在线访问
src/views
目录中,小伙伴可以结合对应的代码来阅读文章。码字不易,如果文章内容对你有帮助的话,希望能点赞鼓励一下作者。
注:有些例子刻意为之,只是为了学习对应的知识点,对于实际使用场景刻意不必深究
props
传参在
Vue
中,我们可以通过为子组件传入props
,然后在子组件中接收,并通过this
来直接访问
{{ count }}
这里我们为
demo-children
传入了count
和add-count
属性,然后又将add-count
传入到demo-grandson
组件中。这样当我们分别点击父组件(demo-props
)、子组件(demo-child
)和孙子组件(demo-grandson
)中的按钮时,都会更新count
属性当然我们也可以使用
v-bind
来直接绑定一个对象,Vue
会帮我们将组件属性进行分发,类似于react
中的{...props}
:文章图片
这张图的出处在这里,有兴趣的小伙伴可以去围观 : https:// twitter.com/tannerlinsl ey/status/1300847251846176768在
Vue
中我们也可以利用这个对象简写的特性来少敲几下键盘:
{{ count }}
自定义事件
Vue
中可以通过@
符号来监听自定义事件,并在子组件中通过$emit
方法来触发监听的事件。我们将上面的例子用自定义事件来进行改写:
{{ count }}
完成上述代码后,我们依旧可以通过点击各个组件内的按钮来更新
count
属性双向绑定
v-model/.sync
Vue
为了方便用户,提供了俩个可以实现双向绑定数据的语法糖。用户不再需要在父组件进行事件监听,便可以实现属性的自动更新。
count: {{ count }}
count1: {{ count1 }}
count2: {{ count2 }}
相比于之前的传参方式,我们不再需要在父组件中监听
addCount
事件来更新父组件中的count
。Vue
会帮我们自动监听对应的事件,并更新属性值。这俩个语法糖的本质如下:
v-model
: 自动绑定value
属性 +input
事件xxx.sync
: 自动绑定update:xxx
事件
模拟实现v-model的count: {{ count }}
模拟实现.sync指令的count: {{ count1 }}
上例中的
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文章图片
到这里,我们使用
v-model/.sync
更简单的实现了功能。$parent/$children
Vue
可以让我们通过$parent/$children
来直接访问到父组件或子组件实例,这样就可以直接使用组件实例中的任意属性和方法。parent:{{ count }}
child:{{ child.count }}
在父组件挂载完成后,通过
this.$chilren[0]
获取到了子组件实例,之后直接通过子组件实例来访问子组件的count
属性。
在子组件中,也可以通过
this.$parent
来直接获取到父组件的count
属性进行更新。$attrs/$listeners
在很多情况下,我们并不需要重新封装一个组件,而是只需要在旧有组件的基础上再添加一些功能。这里我们就用到了
$attrs
和$listenners
属性,而$attrs
又会与inheritAttrs
属性一起使用。先看一下这些属性的用途:
$attrs
: 包含父作用域中绑定的没有被识别或提取为props
的属性(class
和style
除外)inheritAttrs
: 默认的,父作用域中没有被作为props
识别的属性将会"回退",并且作为正常的HTML
属性应用到子组件的根元素。设置inheritAttrs
为false
,将会禁用这个默认行为。$listenners
: 包含父作用域中v-on
绑定的监听器(不包括.native
修饰符绑定的监听器)
demo-grandson
组件,可以接收count
进行展示,并且接受addCount
方法来更新count
。而现在我们想要在不改变
demo-grandson
的基础上,再实现一个组件,它具有demo-grandson
的所有功能,并且还可以展示标题。代码如下:
{{ title }}
子组件中的
$attrs
为除title
外的所有根元素中传入的属性组成的对象,配合inheritAttrs: false
,并不会让其作为正常的HTML
属性在element
中展示。之后再配合v-bind
将属性分发到demo-grandson
上:文章图片
$listeners
中包含v-on
(即@
)中绑定的所有事件监听函数,同理通过v-on
分发到demo-grandson
上:文章图片
这样
demo-grandson
中有再多的属性和事件,我们都可以通过v-bind=$attrs
和v-on=$linstenners
进行传入。而不用每次都在props
中定义,然后再单独在子组件标签上通过:
和@
来进行绑定。这俩个属性在对项目中用到的
ui
组件库进行二次封装时尤其好用,既可以保证使用原有组件所有的api
,还可以额外封装一些项目中复用度高的功能。依赖注入(
provide/inject
)provide/inject
通常用于跨层级传参,不管组件的层级有多深,都可以通过inject
来获得父组件provide
提供的内容。provide/inject
的数据传递思路如下:文章图片
通常情况下,我们会将父组件的实例通过
provide
传入,这样子组件通过inject
就可以直接获取到父组件的实例,从而可以使用到父组件实例中定义的任意属性和方法,我们把之前的例子通过provide/inject
来进行实现:
在子组件中调用
addCount
方法
在孙子组件中渲染
count
到页面中,并且通过按钮来更新count
:child: {{ top.count }}
事件分发(dispatch)和广播(broadcast)
当我们的组件层级比较深的时候,我们需要一层一层向下传递事件,而当更新父组件中的某个属性时,又需要一层一层的将更新向上通知,大概的逻辑如下:
文章图片
为了可以直接通过子组件更新父组件,而不再用经历中间的事件监听步骤,我们可以递归遍历找到父组件的子组件(
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);
}
}
}
};
在原型上添加了对应的方法后,我们便可以在组件中通过组件实例来直接调用:
parent: {{ count }}
great grandson:{{ name }}
上边代码的逻辑大概如下:
文章图片
现在我们便可以通过
$dispatch/$broadcast
来实现跨层级调用$emit
方法,少写一些进行事件监听的@
和$emit
代码。上述代码参考
element ui
源码中$dispatch/$broadcast
的相应实现:文章图片
elememnt ui
并没有将方法挂载到Vue
的原型上,而是定义了mixins
中,最终可以通过mixins
属性来混入到组件中进行使用:文章图片
截图中的代码地址,有兴趣的小伙伴可以点击链接直接查看:
- emitter
- form-item
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
属性:
child: {{ count }}
不管组件层级有多深,我们都可以通过约定好的名字(例子中是
add-count
)来直接调用父组件中的订阅函数。Vuex
对于稍大规模一点的项目来说,通过
Vuex
来管理全局状态是比较好的选择。我们可以在任意组件使用Vuex
中的state
,并且可以通过commit
一个mutation
来更新状态。下面我们用
Vuex
来再次实现count
累加的例子。首先在
store
中定义state
和mutation
: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
模板代码:
{{ count }}
child: {{ count }}
grandson count: {{ count }}
现在我们可以通过
Vuex
辅助函数在实例上提供的count
属性和addCount
方法,就可以在任意组件使用和更新count
。总结
当我们能够熟练掌握组件之间的各种传参技巧后,在实际的工作以及面试中便能够更加的游刃有余、从容不迫。
【vue|vue @input带参数_Vue组件通信详解】我们面临一个实际问题之后,所思考的不再是如何进行组件传参,而是如何能更好的选择和设计一种比较灵活优雅的传参方法,提高程序的可读性和可扩展性。
推荐阅读
- 兼容|vue-cli2在IE浏览器下的兼容问题
- vue|vue学习总结笔记
- vue|vue 学习总结笔记(三)
- 分布式|服务端渲染(SSR) 通用技术解决方案
- vue|如果没有JS框架该怎么办
- java|ios 按时间排序_如何按应用而不是时间对iOS通知进行排序
- 每日三面|每日三道前端面试题--vue 第二弹
- Vue|基于vue+srpingboot的学生成绩管理系统
- bmaplib|bmaplib vue 调用_vue引入百度地图BMapGL,以及辅助工具BMapGLLib 的引入,BMapGL鼠标绘制功能。...