Vue中的组件通信
集中整理一下日常用到的组件传值方案。
一、通过props和$emit实现父子组件传值:
// 父组件
<template>
<div>
// 通过属性方式给子组件传值;监听子组件的change事件
<HelloWorld :msg="text" @change="handleChangeText" />
</div>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
},
data(){
return{
text:'Hello World'
}
},
methods:{
handleChangeText(str){
this.text = str // 将父组件的text属性内容改为子组件传来的值
}
}
}
</script>
// 子组件; 通过$emit触发自定义的change事件
<template>
<div @click="$emit('change','你好,世界')">
{{ msg }}
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String // 需要在此声明有哪些属性需要接收,可设置default默认值
} ,
emits:['change'] // 需要在此声明有哪些事件将会被触发
}
</script>
二、使用EventBus(事件总线)
创建一个EventBus.js文件并向外共享一个Vue的实例对象;
发送方调用bus.$emit('事件名称',数据)触发自定义事件;
接收方使用bus.$on('事件名称',事件处理函数)注册一个自定义事件;
// eventBus.js
import Vue from 'vue'
export default new Vue()
// 数据发送方 header.vue
import bus from '@/utils/eventBus.js'
...
methods:{
send(){
bus.$emit('change','hello')
}
}
// 数据接收方 footer.vue
import bus from '@/utils/eventBus.js'
...
created(){
bus.$on('change', data=>{
console.log('事件被触发,传递值为:', data)
})
}
三、Vuex
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件需要修改数据时,必须通过Mutation进行,Mutation同时提供订阅者模式供外部插件调用获取State的更新。异步操作或批量同步操作需要走Actions,它无法直接修改State,还是需要Mutation修改。
Vuex内保存的数据一刷新就会被清空,可以搭配localStorage进行持久化存储。
// 不需要异步操作,简单的同步更改
// store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
},
mutations: {
addCount(state, num) {
state.count += num;
},
}
});
// HelloWorld.vue
<template>
<div @click="handleAddCount">
{{ count }}
</div>
</template>
<script>
export default {
name: 'HelloWorld',
computed:{
count(){
return this.$store.state.count
}
},
methods:{
handleAddCount(){
//如需同步更改
this.$store.commit('addCount', 3)
}
}
}
</script>
// --------------------------------------
...
this.$store.dispatch('delayAddCount', 2) // or ...mapActions(['delayAddCount'])
... 延迟一秒触发
actions: {
delayAddCount({ commit }, payload) {
setTimeout(() => {
commit("addCount", payload);
}, 1000);
},
},
四、$attrs/$listeners
$attrs中包含的是父组件传递过来但子组件没有接收的属性(style和class除外),可通过v-bind="$attrs"传递给孙子组件。通常和inheritAttrs选项一起使用。
$listeners中包含的是父组件绑定在子组件的事件监听(不包含.native修饰的),可通过v-on="$listeners"传递给孙子组件。
// 父组件 中 给子组件传参
<HelloWorld msg="1123" abc="aaa" />
// 子组件并没有接收,如果不加inheritAttrs:false属性并且不给孙子组件绑定$attrs时,父组件传来的属性会自动成为子组件最外层标签的属性
<template>
<div ref="hhh">
<SonS v-bind="$attrs"></SonS>
</div>
</template>
<script>
import SonS from './SonS.vue';
export default {
name: 'HelloWorld',
// inheritAttrs:false,
components:{
SonS
},
mounted(){
console.log(this.$refs['hhh']); // <div msg="1123" abc="aaa"><div>{}</div></div>
}
// 如果绑定给孙子组件,子组件最外层和孙子组件都会拿到属性,这样是多余的,所以要在子组件加上inheritAttrs:false
// 这样就能把属性和值传递给孙子组件
// 孙子组件
<template>
<div>{{ $attrs }}</div> //{ "msg": "1123", "abc": "aaa" }
</template>
<script>
export default {
name: 'SonS'
}
</script>
五、provide / inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
// 数据不是响应式的,如果修改了父组件的foo,那么子组件的值还是bar不会变。
Vue.observable优化响应式provide 【让一个对象变成响应式数据】
provide(){
this.color = Vue.observable({ data:"#ccc" })
return {
color:this.color
}
}
// 这样父组件中改变color中的data 孙子组件也能同步更改
六、ref与$parent/$children
不推荐 这种方式就是通过获取dom上的属性或方法。
Vue3在setup函数中,props和$emits的写法有所变化
// 父组件
<script setup>
import HelloWorld from './components/HelloWorld.vue'
const handleChange = (data)=>{
console.log(data);
}
</script>
<template>
<HelloWorld msg="Vite + Vue" @change="handleChange" />
</template>
// 子组件
<script setup>
defineProps({
msg: String,
})
const emits = defineEmits(['change'])
</script>
<template>
<h1 @click="emits('change','hello')">{{ msg }}</h1>
</template>
可通过v-model的方式传值
// 父组件
<script setup>
import { ref } from "vue";
import HelloWorld from './components/HelloWorld.vue'
const data = ref('Hello')
</script>
<template>
<HelloWorld v-model:title="data" />
</template>
// 子组件
<script setup>
defineProps({
title: String,
})
const emits = defineEmits(['update:title'])
</script>
<template>
<h1 @click="emits('update:title','你好')">{{ title }}</h1> // update是固定写法
</template>
// 点击h1标签后,显示内容由Hello变为你好
在Vue3中使用ref获取dom的方式来让父组件去操作子组件的属性或方法时,子组件需要使用defineExpose暴露属性or方法。
在setup方法中使用provide和inject
// 父组件
import { provide } from 'vue'
provide('data',data.value)
// 子组件
import { inject } from 'vue'
const data = inject('data')
Vue3中移除了$on、$off,所以如果想用事件总线的方式传值需要借助插件 比如:mitt
新建一个bus.js文件 在里面返回new mitt()
记得在unmounted里移除
mitt.emit('方法名',参数) 触发
mitt.on('方法名',callback) 监听
mitt.off('移除方法名') 移除
在Vue3中Pinia会比Vuex更好
因为官方已经将Pinia作为官方库替换了Vuex的位置
然后 Pinia体积更小、对Ts支持更好、与CompositionAPI紧密结合、采用分离模式每个组件有自己的store实例。
// stores/counter.js
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => {
return { count: 0 };
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++;
},
},
});
// ------
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
// 下面三行语句都可以修改count
counter.count++
counter.$patch({ count: counter.count + 1 })
counter.increment()
</script>
<template>
<!-- 直接从 store 中访问 state -->
<div>Current Count: {{ counter.count }}</div>
</template>