vue组件通信的几种方式

84次阅读

共计 3893 个字符,预计需要花费 10 分钟才能阅读完成。

写在前面
vue 的组件化应该是其最核心的思想了,无论是一个大的页面还是一个小的按钮,都可以被称之为组件。基于 Vue 的开发,就是在写一个个组件,无论是基础组件还是业务组件,最后都是要拼装在一起。按照组件的层级关系,可以把组件之间的关系归纳为:父子关系、隔代关系、兄弟关系,无关联关系。

$ref、$parent、$children

基于当前上下文的,可以通过 $ref、$parent、$children 访问组件实例,可以直接调用组件的方法或访问数据。其中 $parent 可以访问当前组件的父组件,$children 可以访问当前组件的所有子组件。虽然 $parent 和 $children 都可以获取组件实例,但是它们无法在跨级或兄弟间通信,这是它们的缺点。

provide、inject

provide / inject 是 Vue 在 2.2.0 版本后新增的 API。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
也就是在父组件中提供一个值,并且在需要使用的子孙组件中注入改值,即:
// Parent.vue
export default {
provide() {
return {
name: ‘Stone’
}
}
}
// Child.vue
export default {
inject: [‘name’],
mounted() {
console.log(this.name)
}
}
不仅仅是 Child.vue,只要是 Parent.vue 的子组件,无论隔多少代,都可以通过这个的方式注入。这种多级组件透传的方式可以保证单向数据流的清晰性,例如像用户信息这样的全局信息,就可以借助这两个 API 来完成,而没有引入第三方库 vuex。
替代 Vuex
vuex 是把数据集中管理,然后哪里需要就在哪里获取,按照这个思路,我们可以在根组件 App.vue 中注入全局信息,并且在页面的任何地方使用。
// App.vue
<template>
<div>
<router-view></router-view>
</div>
</template>
export default {
provide() {
return {
userInfo: this.user
}
},
data() {
return {
user: {}
}
},
methods: {
getUserInfo () {
$.ajax(‘/user/info’, (data) => {
this.user = data
})
}
}
}
把整个 App.vue 的实例 this 对外提供, 这样其他页面就可以通过 this.userInfo 来获取用户信息。
<template>
<div>
{{userInfo}}
</div>
</template>
<script>
export default {
inject: [‘userInfo’]
}
</script>

$dispatch、$broadcast

这两个 API 是 Vue 1.0 版本的,$dispatch 用于向上级派发事件,$broadcast 用于向下级广播事件的,它们在 2.0 版本中已经被废弃了。
因为基于组件树结构的事件流方式有时让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。
不过我们可以自行实现 dispatch 和 broadcast 方法,用于组件的跨层级通信。它们实现的关键在于如何正确找到所要通信的组件,也就是通过匹配组件的 name 选项向下或向上查找组件。
这两个方法都需要传递 3 个参数,第一个参数是要通信组件的 name 选项值,第二个是自定义事件名称,第三个是要给通信组件传递的值。在 dispatch 里,通过 while 循环不断向上查找父组件,直到查找到 componentName 与某个父级组件的 name 选项匹配时结束,此时改父组件就是要通信的组件。broadcast 方法与 dispatch 类似,是通过 forEach 循环向下查找子组件。最后封装一个 mixins 来便于复用。
// emitter.js
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
通过 mixins 混入组件,实现组件间的通信。
<!– Parent.vue –>
<template>
<button @click=”handleClick”>
触发事件
<Child></Child>
</button>
</template>
<script>
import Emitter from “../assets/js/emitter.js”;
import Child from “./Child.vue”;
export default {
name: “Parent”,
mixins: [Emitter],
created() {
this.$on(“on-message”, message => {
console.log(message);
});
},
components: {
Child
},
methods: {
handleClick() {
this.broadcast(“Child”, “on-message”, “Hello, I am Parent Component”);
}
}
};
</script>
<!– Child.vue –>
<template>
<div></div>
</template>
<script>
import Emitter from “../assets/js/emitter.js”;
export default {
name: “Child”,
mixins: [Emitter],
mounted() {
this.$on(“on-message”, message => {
console.log(message);
this.dispatch(“Parent”, “on-message”, “Copy that, I am Child Component”);
});
}
};
</script>
相比 Vue 1.0 版本内置的两个 API,自行实现的方法有以下不同:

需要额外传入组件的 name 作为第一个参数;
匹配到组件就会停止循环,不会冒泡;
传递的值只能是一个参数,如果需要传递多个参数,只能通过对象或数组的形式;

其他方法
在 vue 中组件的通信还有其他的手法,例如:

props、$emit

<!– Parent.vue –>
<template>
<Child :info=”info”
@update=”onUpdateName”></Child>
</template>
<script>
import Child from “./Child.vue”;
export default {
data() {
return {
info: {
name: “stone”
}
};
},
components: {
Child
},
methods: {
onUpdateName(name) {
this.info.name = name;
}
}
};
</script>
<!– Child.vue –>
<template>
<div>
<div>{{info.name}}</div>
<button @click=”handleUpdate”>update</button>
</div>
</template>
<script>
export default {
props: {
info: {
type: Object,
default: {}
}
},
methods: {
handleUpdate() {
this.$emit(“update”, “new-name”);
}
}
};
</script>

父组件将自己的方法传递给子组件,子组件调用方法传数据给父组件
使用 event bus 事件总线

$attrs、$listeners

Vue 2.4 新增的 API。$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。$listeners 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。
Vuex 集中式状态管理
写在最后
不同的通信方法适用于不同的场景,既可以通过 Vue 内置的 API 来通信,也可以通过自定义事件的方式实现通信方法,当应用足够复杂情况下,就可以使用 Vuex 进行数据管理。

正文完
 0

Vue组件通信的几种方式

84次阅读

共计 3626 个字符,预计需要花费 10 分钟才能阅读完成。

父组件通过 Prop 向子组件传递数据
这个数据流是单向的。数据流向是从父组件传到子组件。也就是说,父级 prop 的更新会向下流动到子组件中,但是反过来则不行。现在我们需要写一个博文组件,展示博文的标题和内容。标题和内容这些数据是从父组件获得的。第一步,定义一个组件,这里用注册全局组件的方式,子组件用 prop 接收来自父组件的数据:
Vue.component(‘blog-post’, {
props: [‘post’],
template: `
<div class=”blog-post”>
<h2>{{post.title}}</h2>
<div v-html=”post.content”></div>
</div>
`
});
第二步,初始化一个 Vue 实例并挂载到对应的 HTML 结构:
// html
<div id=”app”>
<blog-post :post=”post” v-for=”(post, index) in postArr” :key=”index”></blog-post>
</div>

// js
new Vue({
el: “#app”,
data: {
postArr: [
{
title: ‘My name is Judy’,
content: ‘something here ‘
},
{
title: ‘My job is coding’,
content: ‘something here ‘
},
{
title: ‘I like coding’,
content: ‘something here ‘
}
]
}
});
demo:父组件通过 Prop 向子组件传递数据
子组件通过事件向父级组件发送消息
在我们开发 <blog-post> 组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个可访问性的功能来放大博文的字号,同时让其他部分的博文保持默认的字号。还是刚才那个 demo,在其父组件中,我们可以通过给每一条博文的数据添加一个 postFontSize 数据属性来控制每篇博文字体的大小。
// js
new Vue({
el: “#app”,
data: {
postArr: [
{
postFontSize: 1,
title: ‘My name is Judy’,
content: ‘something here ‘
},
{
postFontSize: 1,
title: ‘My job is coding’,
content: ‘something here ‘
},
{
postFontSize: 1,
title: ‘I like coding’,
content: ‘something here ‘
}
]
}
});
现在我们需要给子组件添加一个按钮。当点击这个按钮时,我们需要告诉父级组件放大所有博文的文本。刚好 Vue 实例提供了一个自定义事件的系统来解决这个问题。我们可以调用内建的 $emit 方法并传入事件的名字,来向父级组件触发一个事件。改写一下子组件:
Vue.component(‘blog-post’, {
props: [‘post’],
template: `
<div class=”blog-post”>
<h2>{{post.title}}</h2>
<button @click=”$emit(‘enlarge-text’)”>
Enlarge text
</button>
<div v-html=”post.content”></div>
</div>
`
});
然后我们可以用 v-on 在博文组件上监听这个事件,就像监听一个原生 DOM 事件一样:
<div id=”app”>
<blog-post
@enlarge-text=”post.postFontSize += 0.1″
:post=”post”
v-for=”(post, index) in post_arr”
:key=”index”
:style=”{fontSize: post.postFontSize + ’em’}”
></blog-post>
</div>
demo: 子组件通过事件向父级组件发送消息
以上为常用的父组件向子组件传递数据、子组件通过事件向父级组件发送消息的两种方式。
$parent 访问父级组件实例
$parent 属性可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式,实际上这不是一种数据传递,而是子组件主动发起的数据查找。但是 Vue 并不推荐这么做。在绝大多数情况下,触达父级组件会使得我们的应用更难调试和理解,尤其是我们变更了父级组件的数据的时候。当我们稍后回看那个组件的时候,很难找出那个变更是从哪里发起的。在一些可能适当的时候,我们需要特别地共享一些组件库,就可以考虑用 $parent。Vue 官网称这些情况为【边界情况】。假设有一个 Google 地图组件:
<google-map>
<google-map-markers v-bind:places=”iceCreamShops”></google-map-markers>
</google-map>
这个 <google-map> 组件可以定义一个 map 属性,所有的子组件都需要访问它。在这种情况下 <google-map-markers> 可以通过类似 this.$parent.getMap 的方式访问那个地图,以便为其添加一组标记。点击这里查看官方文档给出的 demo
ref 特性访问子组件实例或子元素
尽管存在 prop 和事件,有的时候仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,我们可以通过 ref 特性为这个子组件赋予一个 ID 引用。例如:
<base-input ref=”usernameInput”></base-input>
现在在已经定义了这个 ref 的组件里就可以使用:
this.$refs.usernameInput
来访问这个 <base-input> 实例,以便不时之需。另外,vm.$children 也可以访问当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。
当我们需要访问子组件或者子元素,推荐使用 ref 而不是 $children。因为 ref 能帮我们访问到的是任意子组件实例或者子元素,$children 只能访问到直接子组件。
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。什么意思呢?在实际开发中,我们会遇到有一处或者多处需要被多个实例间共享的状态,Vuex 就是为我们提供了这样一个模式。所以我的理解是 Vuex 不仅仅是适用于父子组件的通信了,它适用于所有组件共享某些必要的数据状态。关于 Vuex 的使用我们这里不详细展开讨论了,贴一个 Vuex 的文档地址。
一些补充
前面我们提到 google 地图组件,类似这样的:
<google-map>
<google-map-region v-bind:shape=”cityBoundaries”>
<google-map-markers v-bind:places=”iceCreamShops”></google-map-markers>
</google-map-region>
</google-map>
在这个组件里,所有 <google-map> 的后代都需要访问一个 getMap 方法,以便知道要跟那个地图进行交互。不幸的是,使用 $parent 属性无法很好的扩展到更深层级的嵌套组件上,也就是说,只有 <google-map-region> 能通过 $parent 访问到它的直接父组件 <google-map> 的 getMap 方法。这种情况可以用依赖注入 provide 和 inject 解决。
provide 选项允许我们指定我们想要提供给后代组件的数据 / 方法。在这个例子中,就是 <google-map> 内部的 getMap 方法:
provide: function () {
return {
getMap: this.getMap
}
}
然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的属性:
inject: [‘getMap’]
点击这里查看官方文档的 demo
相比 $parent 来说,这个用法可以让我们在任意后代组件中访问 getMap,而不需要暴露整个 <google-map> 实例。这允许我们更好的持续研发该组件,而不需要担心我们可能会改变 / 移除一些子组件依赖的东西。同时这些组件之间的接口是始终明确定义的,就和 props 一样。
实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:

祖先组件不需要知道哪些后代组件使用它提供的属性
后代组件不需要知道被注入的属性来自哪里

我看了很多篇总结 Vue 父子组件通信方式的博客都没有提到依赖注入,不过我觉得依赖注入也实现了通信——由父组件通过 provide 把数据暴露出去,子组件通过 inject 接收数据。
以上就是我阅读官方文档总结出来的 Vue 组件通信的几种方式,大部分内容和 demo 都来源于官网文档,了解更多可以阅读文档:Vue 文档地址。文章全部内容仅代表个人观点,欢迎批评或者与我讨论。感谢你的阅读。

正文完
 0