相比React受控组件的概念,Vue组件之间的“通信”特征则表现的更强。
这种“通信”特性表现在,组件内部拥有一套自己的状态管理,对外只表现为信息的传递,而不需要外部组件特意做出什么行为(例如受控组件的状态完全是由控制者决定,受控组件状态的改变必须要求控制者做出响应的行为)
除了常见的父子级别通信,还有兄弟级、跨级,到总线和Vuex。不得不说Vue组件通信的方式要更丰富一些。
父组件给子组件传消息
1. 属性props
最基础的父传子手段。
// child指定props
export default {
props: {
msg: String
}
}
// parent传递消息
<child msg='msg' />
2. 引用ref和children
直接获取子组件引用来设置子组件的内部值。children是一个数组,装着所有自定义子组件,但多个子组件情况下排列不能保证顺序。所以更可靠的还是子组件制定ref,父组件使用$refs获取子组件引用。
// child
export default {
data() {
return {
// parent msg by $refs
// or
// parent msg by $children
current: ''
}
}
}
// parent
<child ref="child">
export default {
methods: {
handleChange1() {
this.$refs.child.current = 'parent msg by $refs'
},
handleChange2() {
this.$children[0].current = 'parent msg by $children'
}
},
mounted() {
// this.handleChange1()
// or
// this.handleChange2()
}
}
子组件给父组件传消息
1. 自定义事件
最核心的向上传递消息的手段
// child
<button @click="sendMsg" />
export default {
methods: {
sendMsg() {
this.$emit('sendMsg', { msg: 'msg' })
}
}
}
// parent
<child @send-msg="receiveMsg" />
export default {
methods: {
receiveMsg(param) {
console.log(param) // { msg: 'msg' }
}
}
}
v-model和.sync的本质也是props➕自定义事件的组合
祖先组件给后代组件传消息
1. 特性$attr
传递那些子组件没有设定在props里面的值,通常在子组件中使用inheritAttrs: false来取消非props值在根组件上的继承。不过$attrs特殊的是,可以在“族谱”中一直向后传递信息:每个成员使用v-bind="$attrs"都可以将祖先的非props属性传递下去。
// grandson
export default {
mounted() {
console.log(this.$attrs.attr) // attr
}
}
// child
<grandson v-bing="$attrs" />
export default {
props: {
msg: String
},
mounted() {
console.log(this.msg, this.$attrs.attr) // props, attr
}
}
// parent传递props消息和attrs消息
<child msg="prop" attr="attr" />
2. 提供和注入provide/inject
一个祖先向所有后代提供消息的手段,不必由每一代传递消息
// grandson
export default {
inject: ['msg'],
mounted() {
console.log(this.msg) // 'msg'
}
}
// child
<grandson />
// parent
<child />
export default {
provide() {
return {
msg: 'msg'
}
}
}
后代组件给祖先组件传消息
1. 监听器$listeners
$listeners可以一直向后传递事件(普通事件和自定义事件),由某一后代触发事件,实现向祖先传递消息
// grandson
<button @click="sendMsg">触发祖先的自定义事件</button>
export default {
methods: {
sendMsg() {
this.$listeners['on-send']({ grandson: 'grandson' })
}
}
}
// child
<grandson v-on="$listeners"/>
// parent
<child @on-send="receiveMsg" />
export default {
methods: {
receiveMsg(param) {
console.log(param) // { grandson: 'grandson' }
}
}
}
兄弟组件传消息
1. 借助共同父代或祖先
// brother-send
export default {
mounted() {
this.$parent.$emit('parent-msg', { msg: 'msg by parent' })
this.$root.$emit('root-msg', { msg: 'msg by root' })
}
}
// brother-receive
export default {
created() {
this.$parent.$on('parent-msg', (param) => {
console.log(param) // { msg: 'msg by parent' }
})
this.$root.$on('root-msg', (param) => {
console.log(param) // { msg: 'msg by root' }
})
},
destroyed() {
this.$parent.$off('parent-msg')
this.$root.$off('root-msg')
}
}
// parent
<div>
<brother-send />
<brother-receive />
</div>
无规律跨级通信
1. 事件总线Bus
可以直接借助一个空Vue对象,自带$on $emit $off的API
// bus.js
export default new Vue()
也可以自己构造
// bus.js
class Bus {
constructor() {
this.callbacks = {}
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, ...args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach(cb => cb(...args))
}
}
$off(name) {
this.callbacks[name] = null
Reflect.deleteProperty(this.callbacks, name)
}
}
export default new Bus()
状态管理
Vuex能解决所有组件之间的通信问题,但实际上Vuex更像是一个状态管理的“库”。Vuex相对比较重,但是能保存所有需要的数据结构,并且所有组件都可以访问到。是否使用Vuex还是取决于项目规模,或者说是数据规模。