Vue2 组件通信方式
1.props(父传子)
Father.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<Son msg="这是父组件传过来的数据"/>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
components: {
Son
}
}
</script>
子组件中用 props:{参数1: String, 参数2: Number} 或 props: ['参数1', '参数2'] 来接收父组件传过来的数据:
Son.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
// 也可直接写成props: ['msg']
}
</script>
2.$emit(子传父)
子组件通过调用父组件传过来的方法传递数据给父组件。
Father.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<h1>sonValue:{{sonValue}}</h1>
<Son msg="这是父组件传过来的数据" @receiveValue="receiveValue" />
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {sonValue: ''}
},
components: {
Son
},
methods: {
receiveValue(data) {
this.sonValue = data;
}
}
}
</script>
子组件中调用 this.$emit('父组件传来的方法名', '值1', '值2', ...) 方法给父组件传递数据:
Son.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="onClick">给父组件传数据</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['msg'],
methods: {
onClick() {
this.$emit('receiveValue', '子组件数据');
}
}
}
</script>
3.$refs
在子组件上标记 ref 属性,通过this.$refs.xxx直接访问组件的属性或方法或DOM元素。
Father.vue
<template>
<div id="app">
<Son ref="sonRef" />
<button @click="btnClick">点我一下获取子组件属性和方法</button>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
components: {
Son
},
methods: {
btnClick() {
console.log('sonRef', this.$refs.sonRef);
}
}
}
</script>
4.全局事件总线(跨层级、兄弟)
创建一个空的Vue实例作为中央事件总线,通过$on监听事件,$emit触发事件。
EventBus.vue
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
发送方调用 EventBus.$emit() 来发送数据:
Son1.vue
<template>
<div class="hello">
<button @click="onBtnClick">给兄弟组件传数据</button>
</div>
</template>
<script>
import EventBus from './EventBus.ts';
export default {
name: 'HelloWorld',
methods: {
onBtnClick() {
EventBus.$emit('bro-value-1', {a: 'son1给son2的数据'});
}
}
}
</script>
接收方用 EventBus.$on() 来接收数据:
Son2.vue
<template>
<div class="hello">
<h1>Son2--接收到的数据:{{ msg }}</h1>
</div>
</template>
<script>
import EventBus from "./EventBus.ts";
EventBus.$on("bro-value-1", (payload) => {
console.log("来自son1的数据", payload);
});
export default {
name: "sonTwo",
data() {
return { msg: "" };
},
};
</script>
5.$parent和/$children
父组件通过 this.$children 访问子组件实例(注意:此处得到的是数组,且数组顺序不固定);
子组件通过 this.$parent 访问父组件实例。
6.provide和inject(可跨层级)
父代传递数据用 provide(){return {a: this.a}} 来给后代传递数据,如:
Father.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<h1>sonValue:{{sonValue}}</h1>
<Son />
<Son2 />
</div>
</template>
<script>
import Son from './components/Son.vue';
import Son2 from './components/Son2.vue';
export default {
name: 'App',
data() {
return {sonValue: '', fromFather: 'Father数据'}
},
components: {
Son,
Son2
},
methods: {
btnClick() {
console.log('sonRef', this.$children);
}
},
provide(){
return {
fromFather: this.fromFather,
}
}
}
</script>
子代通过 inject: ['a'] 的方式来获取数据,如:
Son.vue
<template>
<div class="hello">
<h1>来自Father.vue的inject注入:{{ fromFather }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['msg'],
data() {
return {a: 'son-value1', b: 'son-value2',}
},
inject: ['fromFather']
}
</script>
一般来讲,它们主要用于跨层级传递数据,如果兄弟组件之间一定要用它进行通信,可以将需要共享的数据放在共享的祖先组件上,然后通过provide和inject来提供给需要的兄弟组件。
7.$attrs和$listeners(可跨层级)
$attrs接收未被props识别的父组件属性,而$listeners接收父组件的事件监听器 / 方法。
Father.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<br />
grandson传回来的值:{{grandSonName}}
<Son name='来自Father.vue的数据' level='1' @onChangeGrandsonName="onChangeGrandsonName" />
</div>
</template>
<script>
import Son from './components/Son.vue';
export default {
name: 'App',
data() {
return {grandSonName: ''}
},
components: {
Son
},
methods: {
onChangeGrandsonName(value) {
this.grandSonName = value;
},
},
}
</script>
在Son.vue中若用了props: ['level', 'name'],则GrandSon.vue中的$attrs则接收不到任何属性;
Son.vue
<template>
<div class="hello">
<GrandSon v-bind="$attrs" v-on="$listeners" />
</div>
</template>
<script>
import GrandSon from './GrandSon.vue';
export default {
name: 'HelloWorld',
components: {
GrandSon
},
}
</script>
GrandSon.vue
<template>
<div class="hello">
来自Father.vue的数据:{{$attrs.name}}
<br/>
<button @click="btnClick">点击给Father.vue回传数据</button>
</div>
</template>
<script>
export default {
name: 'GrandSon',
methods: {
btnClick() {
this.$emit('onChangeGrandsonName', "This is grandson's name");
this.$emit('onHandleChange')
}
}
}
</script>
8.状态管理(Vuex)
状态管理库,通过state存储数据,mutations同步修改数据,actions处理异步操作。
store.ts
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
msg: '11'
},
mutations: {
setMessage(state, message) {
state.msg = message;
}
},
actions: {
asyncIncrement({commit}, value) {
setTimeout(() => commit('setMessage', value), 1000);
}
}
});
export default store;
Father.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<br />
msg值:{{$store.state.msg}}
<br />
<br />
<button @click="onHandleChange('mutaions')">触发mutaions</button>
<br />
<br />
<button @click="onHandleChange('actions')">触发actions</button>
</div>
</template>
<script>
import store from './components/store.ts';
export default {
name: 'App',
store, // 引入store
data() {
return {msg: ''}
},
methods: {
onHandleChange(type) {
if (type === 'mutaions') {
this.$store.commit('setMessage', '通过mutations修改msg的值')
} else if (type === 'actions') {
this.$store.dispatch('asyncIncrement', '通过actions修改msg的值')
}
},
},
}
</script>
Vue3 组件通信方式
1.props(父传子)
在vue3中可以通过defineProps获取父组件传递的数据,且在组件内部不需要引入defineProps方法直接使用即可。
父组件代码:
<Child info="我爱祖国" :money="money"></Child>
子组件接收数据代码:
方式一:
let props = defineProps({
info:{
type:String,//接受的数据类型
default:'默认参数',//接受默认数据
},
money:{
type:Number,
default:0
}})
方式二:
let props = defineProps(["info",'money']);
props是只读的,props中的值不能被修改。
2.自定义事件(子传父)
在vue框架中事件分为两种:一种是原生的DOM事件,另外一种自定义事件;
原生DOM事件可以让用户与网页进行交互,比如click、dbclick、change、mouseenter、mouseleave....
自定义事件可以实现子组件给父组件传递数据
2.1 原生dom事件
<!--此处会默认给事件回调注入event事件对象 -->
<pre @click="handler">
我是祖国的花朵
</pre>
<!--注入多个参数的写法,注意:注入的事件对象必须叫做$event -->
<div @click="handler1(1,2,3,$event)">我要传递多个参数</div>
在vue3框架中,click、change等这类原生DOM事件,不管是在标签、自定义标签上(组件标签)都是原生DOM事件。而vue2中却需要通过 native修饰符 才能变为原生DOM事件。
2.2 自定义事件
父组件给子组件Child 绑定一个自定义事件myHandle:
<Child @myHandle="handler3" @click="handler"></Child>
在 Child 子组件内触发这个 myHandle 并传递数据给父组件的handler3方法:
<template>
<div>
<h1>我是子组件2</h1>
<button @click="handler">点击我触发myHandle自定义事件</button>
</div>
</template>
<script setup lang="ts">
// defineEmits方法无需引入,直接使用
let $emit = defineEmits(["myHandle"]);
const handler = () => {
// 第一个参数:事件类型;第二、三、四...个参数:注入数据(传递的数据)
$emit("myHandle", "法拉利", "茅台");
};
</script>
<style scoped>
</style>
defineEmits 是vue3提供的方法,可直接使用,无需import,执行此方法时传递一个数据,数组中的元素几位将来组件需要触发的自定义事件类型。
当点击按钮的时候,事件回调内部调用$emit方法去触发自定义事件,第一个参数为触发事件类型,第2、3、...、n个参数即为传递给父组件的数据。
注意:如果在Child子组件内写成
let $emit = defineEmits(["myHandle",'click']);即把click方法也写进去,则click也会一起变为自定义事件。
3.全局事件总线(任意组件的通信)
可实现任意组件间的通信,包括兄弟组件,在vue3中通过第三方库 mitt 实现。
具体实现:
bus.ts
// 引入mitt插件:mitt一个方法,方法执行会返回bus对象
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
Child1.vue
<script setup lang='ts'>
import $bus from '../bus';
// 组合式API
import {onMounted} from 'vue';
// 组件挂在完毕时,当前组件绑定一个事件,接收将来兄弟组件传递的数据
onMounted(() => {
// 第一个参数为事件类型;第二个参数为事件回调,params为接收的数据
$bus.on('car', (params) => {
console.log(params) // 这里将输出:{car: '法拉利'}
})
})
</script>
Child2.vue
<template>
<div>
<h2>我是子组件2</h2>
<button @click='handler'>点击我给兄弟送一台法拉利</button>
</div>
</template>
<script setup lang='ts'>
// 引入$bus对象
import $bus from '../bus';
// 点击按钮回调
const handler = () => {
// 第一个参数为事件类型;第二个参数为传递的参数
$bus.emit('car', {car: '法拉利'})
}
</script>
4.v-model
v-model除了可以收集表单数据(数据双向绑定)外,还可用来实现父子组件的数据同步。它实际上是利用 props[modelValue] 与自定义事件 [update:modelValue] 来实现的。
<!-- 相当于给组件Child传递一个props[modelValue]与绑定一个自定义事件update:modelValue -->
<Child v-model="msg"></Child>
<!-- 相当于给组件Child传递两个props分别是pageNo与pageSize,以及绑定两个自定义事件update:pageNo与update:pageSize实现父子数据同步 -->
<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>
5.useAttrs(父传子)
在Vue3中可以利用 useAttrs 方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件),此函数功能类似于Vue2框架中 $attrs 属性与 $listeners 方法。
比如:在父组件内部使用一个子组件my-button:
<my-button type="success" size="small" title='标题' @click="handler"></my-button>
子组件my-button中通过 useAttrs 来获取组件属性和方法(使用前需要先import):
<template>
<!-- 简写,表示:<el-button type="$attrs.type size=$atttrs.size"></el-button>-->
<el-button :="$attrs"></el-button>
</template>
<script setup lang="ts">
import {useAttrs} from 'vue';
let $attrs = useAttrs();
</script>
useAttrs 类似于props,可以用于接收父组件传递过来的属性与属性值。需要注意:如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值。
6.ref和$parent
6.1 ref
ref 可以获取元素的DOM或者获取子组件实例的VC。
在父组件挂载完毕获取子组件实例:
father.vue
<template>
<div>
<h1>ref与$parent</h1>
<Son ref="son"></Son>
</div>
</template>
<script setup lang="ts">
import Son from "./Son.vue";
import { onMounted, ref } from "vue";
const son = ref();
onMounted(() => {
console.log(son.value);
});
</script>
Son.vue
<script setup lang="ts">
import { ref } from "vue";
//数据
let money = ref(1000);
//方法
const handler = ()=>{
}
defineExpose({
money,
handler
})
</script>
注意:与Vue2不同的是,Vue3中如果子组件中的某些数据或者方法要给父组件访问,则需要在子组件中用 defineExpose 对外暴露,因为vue3中组件内部的数据对外是“关闭的”,外部不能访问。
6.2 $parent
$parent 可以获取某一个组件的父组件实例VC(必须要有一个触发事件,比如点击某个按钮时获取父组件实例)
Son.vue
<button @click="handler($parent)">点击我获取父组件实例</button>
同理,父组件要传给子组件的数据与方法一样需要通过defineExpose方法对外暴露。
7.provide和inject(可跨层级)
provide方法用于提供数据,此方法需要传递两个参数,分别为key值与value值:
<script setup lang="ts">
import {provide} from 'vue'
// 两个参数,参数1:提供的数据的key值;参数2:祖先组件提供的数据
provide('token','admin_token');
</script>
后代组件可以通过inject方法获取数据,通过key获取存储的数值
<script setup lang="ts">
import {inject} from 'vue'
// 需要的参数:祖先提供的数据的key值
let token = inject('token');
</script>
与props的区别:inject接收到的数据值可以被修改,而props传递的数据是只读的。
8.状态管理(pinia)
pinia:集中式管理状态容器,可以实现任意组件间的通信,类似于VueX,但核心概念中没有mutation、modules。
核心概念:state(存储数据)、actions(处理业务、异步)、getters(计算属性)。
使用方法一(选项式):
(大仓库)路由:store/index.ts
// 创建大仓库,大仓库管理小仓库
import { createPinia } from 'pinia';
// createPinia可用于创建大仓库
let store = createPinia();
// 对外暴露,安装仓库
export default store;
入口文件:main.ts
// 引入仓库
import store from '../store';
app.use(store); // 注册仓库
(小仓库1)路由:store/modules/info.ts
// 定义info小仓库
import { defineStore } from 'pinia';
// 第一个参数:小仓库名字;第二个参数:小仓库配置对象
// defineStore方法执行会返回一个函数,函数作用就是让组件可以获取到仓库数据
let useInfoStore = defineStore("info", {
// 存储数据store
state: () => {
return {
count: 99,
arr: [1, 2, 3, 4, 5]
}
},
action: {
updateCount(a:number) {
// 注意:函数没有context上下文对象
// 没有commit、mutation
console.log(this);
this.count = a;
// this.count ++
}
},
getters: {
total() {
let result:any = return this.arr.reduce((pre:number,next:number) => {
return pre + next;
}, 0);
return result;
}
}
});
// 对外暴露方法
export default useInfoStore;
实际使用页面,eg:Child.vue
import useInfoStore from '../../store/modules/Info.ts';
// 获取小仓库对象
const infoStore = useInfoStore();
// 修改数据方法
const updateInfoCount = () => {
// 写法一:直接修改
infoStore.count++;
// 写法二:调用$patch方法
infoStore.$patch({
count: 111
});
// 写法三:调用仓库中定义的方法
infoStore.updateCount(111); // 可以传递数据
};
const total = infoStore.total;
使用方法二(组合式):
(小仓库2)路由:store/modules/Todo.ts
// 定义组合式API仓库
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
// 创建小仓库
let useTodoStore = defineStore("todo", () => {
let todos = ref([
{id: 1, title: '吃饭'},
{id: 2, title: '睡觉'},
{id: 3, title: '打豆豆'},
]);
let att = ref([1, 2, 3, 4, 5]);
// 组合式API的计算属性用computed
const total = computed(() => {
arr.value.reduce((pre:any, next:any) => {
return pre + next;
}, 0)
})
// 务必要返回一个对象:属性与方法可以提供给组件使用
return {
todos,
updateTodo() {
todos.value.push({id: 4, title: '4444444'})
},
total
}
});
export default useTodoStore;
实际使用页面,eg:Child.vue
// 引入组合式API函数仓库
import useTodoStore from '../../store/modules/Todo.ts';
// 获取小仓库对象
const todoStore = useTodoStore();
// 修改仓库数据
const updateTodo = () => {
todoStore.updateTodo(111); // 可以传递数据
}
VueX:集中式管理状态容器,实现任意组件间的通信;
核心概念:state(存储数据)、mutation(唯一修改数据的地方)、actions(处理业务、异步)、getters(计算属性)、modules(模块式开发);
pinia和VueX的缺点:存储的数据并不持久化。
9.slot(插槽,父子通信)
9.1 默认插槽
默认插槽一般只有一个
Son.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
Father.vue
<Son>
<!-- <Son></Son>标签内的内容会显示在子组件Son.vue中 -->
<h1>我是默认插槽填充的结构</h1>
</Son>
9.2 具名插槽
具名插槽可以有多个,子组件中用<slot name="a"></slot>中的name来指定插槽名字。
Son.vue
<template>
<div>
<h1>todo</h1>
<slot name="a"></slot>
<slot name="b"></slot>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
父组件中用 v-slot: (可直接简写为 #)来向指定的具名插槽传递结构
Father.vue
<template>
<div>
<h1>slot</h1>
<Son>
<template v-slot:a> //可以用#a替换
<div>填入组件A部分的结构</div>
</template>
<template v-slot:b>//可以用#b替换
<div>填入组件B部分的结构</div>
</template>
</Son>
</div>
</template>
<script setup lang="ts">
import Son from "./Son.vue";
</script>
<style scoped>
</style>
9.3 作用域插槽
子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)。
特点:可以将数据回传给父组件,父组件可以决定这些回传的数据是以何种结构或外观在子组件内部去展示。
Son.vue
<template>
<div>
<h1>todo</h1>
<ul>
<!--组件内部遍历数组-->
<li v-for="(item,index) in todos" :key="item.id">
<!--作用域插槽将数据回传给父组件-->
<slot :$row="item" :$index="index"></slot>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
defineProps(['todos']);//接受父组件传递过来的数据
</script>
<style scoped>
</style>
Father.vue
<template>
<div>
<h1>slot</h1>
<Son :todos="todos">
<template v-slot="{$row,$index}">
<!--父组件决定子组件的结构与外观-->
<span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span>
</template>
</Son>
</div>
</template>
<script setup lang="ts">
import Son from "./Todo.vue";
import { ref } from "vue";
//父组件内部数据
let todos = ref([
{ id: 1, title: "吃饭", done: true },
{ id: 2, title: "睡觉", done: false },
{ id: 3, title: "打豆豆", done: true },
]);
</script>
<style scoped>
</style>
总结
Vue2和Vue3提供了多种组件通信方式:
- 父子通信:props/$emit、$refs、v-model
- 跨级通信:provide/inject、$attrs/$listeners
- 全局通信:事件总线、Vuex/pinia状态管理
- 其他方式:$parent/$children、插槽(默认/具名/作用域)
Vue3新增特性:
- 组合式API:defineProps、defineEmits
- 新工具:useAttrs替代$attrs/$listeners
- 推荐使用pinia替代Vuex
不同场景适用不同通信方式: 简单父子传值用props/$emit 跨多级组件用provide/inject 复杂状态管理用pinia/Vuex。
除了上述提到的这些Vue专用的通信方式之外,还有像window.postMessage之类的方式来进行不同窗口间的通信,欢迎大家前来讨论补充!
1044

被折叠的 条评论
为什么被折叠?



