写在前面
组件间的通信是是实际开发中非常常用的一环,如何使用对项目整体设计、开发、规范都有很实际的的作用,我在项目开发中对此深有体会,总结下 vue 组件间通信的几种方式,讨论下各自的使用场景
文章对相关场景预览
父 -> 子组件间的数据传递
子 -> 父组件间的数据传递
兄弟组件间的数据传递
组件深层嵌套,祖先组件与子组件间的数据传递
文章相关技术预览 prop、emit、bus、vuex、路由 URL、provide/inject
注:以下介绍与代码环境:vue2.0+、vue-cli2
父 -> 子组件间的数据传递
父子组件的通信是开发是最常用的也是最重要的,你们一定知道父子通信是用 prop 传递数据的, 像这样:
// 父组件, 传递数据
<editor :inputIndex=”data” :inputName=” 王文健 ”></editor>
// 子组件,接受数据,定义传递数据的类型 type 与默认值 default
props: {
inputIndex: {
type: Object,
default: function(){
return {}
}
},
inputName: {
type: String,
default: ”
},
注意项:
父组件传递数据时类似在标签中写了一个属性,如果是传递的数据是 data 中的自然是要在传递属性前加 v -bind:,如果传递的是一个已知的固定值呢
字符串是静态的可直接传入无需在属性前加 v -bind
数字,布尔,对象,数组,因为这些是 js 表达式而不是字符串,所以即使这些传递的是静态的也需要加 v -bind,把数据放到 data 中引用,
如果 prop 传到子组件中的数据是一个对象的话,要注意传递的是一个对象引用,虽然父子组件看似是分离的但最后都是在同一对象下
如果 prop 传到子组件的值只是作为初始值使用,且在父组件中不会变化赋值到 data 中使用
如果传到子组件的 prop 的数据在父组件会被改变的,放到计算属性中监听变化使用。因为如果传递的是个对象的话,只改变下面的某个属性子组件中是不会响应式更新的,如果子组件需要在数据变化时响应式更新那只能放到 computed 中或者用 watch 深拷贝 deep:true 才能监听到变化
当然如果你又需要在子组件中通过 prop 传递数据的变化做些操作,那么写在 computed 中会报警告,因为计算属性中不推荐有任何数据的改变,最好只进行计算。如果你非要进行数据的操作那么可以把监听写在 watch(注意 deep 深拷贝)或者使用 computed 的 get 和 set 如下图:
但问题又来了,如果你传进来的是个对象,同时你又需要在子组件中操作传进来的这个数据,那么在父组件中的这个数据也会改变,因为你传递的只是个引用,即使你把 prop 的数据复制到 data 中也是一样的,无论如何赋值都是引用的赋值,你只能对对象做深拷贝创建一个副本才能继续操作,你可以用 JSON 的方法先转化字符串在转成对象更方便一点,
所以在父子传递数据时要先考虑好数据要如何使用,否则你会遇到很多问题或子组件中修改了父组件中的数据,这是很隐蔽并且很危险的
子 -> 父组件间的数据传递
在 vue 中子向父传递数据一般用 $emit 自定义事件,在父组件中监听这个事件并在回调中写相关逻辑
// 父组件监听子组件定义的事件
<editor :inputIndex=”index” @editorEmit=’editorEmit’></editor>
// 子组件需要返回数据时执行,并可以传递数据
this.$emit(‘editorEmit’, data)
那么问题来了,我是不是真的有必要去向父组件返回这个数据,用自定义事件可以在当子组件想传递数据或向子组件传递的数据有变化需要重新传递时执行,那么另外一种场景,父组件需要子组件的一个数据但子组件并不知道或者说没有能力在父组件想要的时候给父组件,那么这个时候就要用到组件的一个选项 ref:
<editor ref=”editor” @editorEmit=’editorEmit’></editor>
父组件在标签中定义 ref 属性,在 js 中直接调用 this.$refs.editor 就是调用整个子组件,子组件的所有内容都能通过 ref 去调用,当然我们并不推荐因为这会使数据看起来非常混乱,
所以我们可以在子组件中定义一种专供父组件调用的函数,,比如我们在这个函数中返回子组件 data 中某个数据,当父组件想要获取这个数据就直接主动调用 ref 执行这个函数获取这个数据,这样能适应很大一部分场景,逻辑也更清晰一点
另外,父向子传递数据也可以用 ref,有次需要在一个父组件中大量调用同一个子组件,而每次调用传递的 prop 数据都不同,并且传递数据会根据之后操作变化,这样我需要在 data 中定义大量相关数据并改变它,我可以直接用 ref 调用子组件函数直接把数据以参数的形式传给子组件,逻辑一下子清晰了
如果调用基础组件可以在父组件中调用 ref 执行基础组件中暴露的各种功能接口,比如显示,消失等
兄弟组件间的数据传递
vue 中兄弟组件间的通信是很不方便的,或者说不支持的,那么父子组件中都有什么通信方式呢
路由 URL 参数
在传统开发时我们常常把需要跨页面传递的数据放到 url 后面,跳转到另外页面时直接获取 url 字符串获取想要的参数即可,在 vue 跨组件时一样可以这么做,
// router index.js 动态路由
{
path:’/params/:Id’,
component:Params,
name:Params
}
// 跳转路由
<router-link :to=”/params/12″> 跳转路由 </router-link>
在跳转后的组件中用 $route.params.id 去获取到这个 id 参数为 12,但这种只适合传递比较小的数据,数字之类的
Bus 通信
在组件之外定义一个 bus.js 作为组件间通信的桥梁,适用于比较小型不需要 vuex 又需要兄弟组件通信的
bus.js 中添加如下
import Vue from ‘vue’
export default new Vue
组件中调用 bus.js 通过自定义事件传递数据
import Bus from ‘./bus.js’
export default {
methods: {
bus () {
Bus.$emit(‘msg’, ‘ 我要传给兄弟组件们 ’)
}
}
}
兄弟组件中监听事件接受数据
import Bus from ‘./bus.js’
export default {
mounted() {
Bus.$on(‘msg’, (e) => {
console.log(e)
})
}
}
注:以上两种使用场景并不高所以只是简略提一下,这两点都是很久以前写过,以上例子网上直接搜集而来如有错误,指正
Vuex 集中状态管理
vuex 是 vue 的集中状态管理工具,对于大型应用统一集中管理数据,很方便,在此对 vuex 的用法并不过多介绍只是提一下使用过程中遇到的问题
规范:对于多人开发的大型应用规范的制定是至关重要的,对于所有人都会接触到的 vuex 对其修改数据调用数据都应有一个明确严格的使用规范
vuex 分模块:项目不同模块间维护各自的 vuex 数据
限制调用:只允许 action 操作数据,getters 获取数据,使用 mapGetters,mapActions 辅助函数调用数据
对于 vuex 的使用场景也有一些争论,有人认为正常组件之间就是要用父子组件传值的方式,即使子组件需要使 vuex 中的数据也应该由父组件获取再传到子组件中,但有的时候组件间嵌套很深,只允许父组件获取数据并不是一个方便的方法,所以对于祖先元组件与子组件传值又有了新问题,vue 官网也有一些方法解决,如下
祖先组件与子组件间的数据传递
provide/inject 除了正常的父子组件传值外,vue 也提供了 provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效
官网实例
// 父级组件提供 ‘foo’
var Provider = {
provide: {
foo: ‘bar’
},
// …
}
// 子组件注入 ‘foo’
var Child = {
inject: [‘foo’],
created () {
console.log(this.foo) // => “bar”
}
}
provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
一个字符串数组,或一个对象,对象的 key 是本地的绑定名,value 是:
在可用的注入内容中搜索用的 key (字符串或 Symbol),或 一个对象,该对象的:
from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
default 属性是降级情况下使用的 value
提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
具体细节移步 vue 相关介绍 https://cn.vuejs.org/v2/api/#…
provide/inject 还未在项目中应用过,后面会做尝试
写在结尾
文章只是整理一下笔记,谈一谈遇到的问题和经验,并没有严谨的措辞和详细的过程,如有错误望指正
原创文章转载引用请注明原文链接 http://blog.wwenj.com/index.p…