Vue 3.0父子组件通信
在Vue 3.0 发布以后,我们基于新的特性,来归纳一下父子组件通信的方式。并且检验一下Vue 2.0中常用的通信方式,如何在Vue 3.0中使用。本文列出了三种通信方式:通过emit函数派发消息父组件通过事件代理获取子组件内容通过vuex定义全局数据设计和实现本文通过一个具体实例来说明具体的通信方式,用户可以将tab数据和初始默认选中的tab-item作为属性传入tabs组件。组件支持按月查询销
·
在Vue 3.0 发布以后,我们基于新的特性,来归纳一下父子组件通信的方式。并且检验一下Vue 2.0中常用的通信方式,如何在Vue 3.0中使用。本文列出了三种常用的通信方式:
- 子组件通过emit函数派发消息给父组件
- 父组件通过事件代理获取子组件内容
- 通过vuex实现父子组件数据共享
- 通过依赖注入实现父子组件数据共享
设计和实现
本文通过一个Tabs组件实例来说明父子组件的通信方式,用户可以将Tabs数据和初始默认选中的Tab项作为属性传入Tabs组件。组件中每个月份为一个Tab项,每个Tab项内包含当月的销量数据。
点击Tabs项的链接,页面显示对应月份的销量数据。具体如下图所示:
根据上述需求,Tabs组件包括TabItem和TabContent两个子组件。
- TabItem 显示月份信息
- TabContent 显示指定月份的销量数据
- Tabs,TabItem和TabContent共享当前点击的月份信息,即哪个Tabs项被选中。
- 数据结构请参看附录
最终实现的用法如下,data见附录:
<tabs :data="data" :initialIdx="1" >
Tabs组件目录接构如下:
- index.vue Tabs的最外层组件,可以接收用户传入的Tabs数据和默认选中的Tabs项
- tab-item.vue Tabs的子组件,用于显示每个Tabs项的月份信息
- tab-content.vue Tabs的子组件,用于显示每个Tabs项的销量数据
基于emit函数实现父子组件通信
父组件Tabs关键代码如下:
<template>
<div>
<div class="Tabs">
<tab-item
v-for = "item in data"
:key = "item.Id"
:text = "item.Month"
:curIdx = "CurrentIdx"
@click-item = "clickItem"
>
</tab-item>
</div>
<tab-content :curIdx = "CurrentIdx" :data = "data"></tab-content>
</div>
</template>
<script>
import TabItem from "./tab-item";
import TabContent from "./tab-content";
import {onMounted, reactive, toRefs} from "vue";
export default {
name: "Tabs",
props:{
data:{
type:Array,
default:()=>[]
},
initialIdx: {
type:Number,
default:1
}
},
components:{
TabItem,
TabContent
},
setup(){
const state = reactive({CurrentIdx:0});
onMounted(()=>{
state.CurrentIdx = props.initialIdx;
});
const clickItem = (index)=>{
state.CurrentIdx = index;
};
return {
...toRefs(state),
clickItem
};
}
}
</script>
- CurrentIdx定义为响应式数据,子组件通过curIdx属性获取当前选中的tab项。
- onMounted是composition API,使用方式和Vue2.0的钩子函数类似。
- …toRefs(state)将响应式数据平铺,以便于视图中绑定CurrentIdx。
子组件TabItem关键代码如下:
<template>
<div class="tab-item" @click = "clickItem()">
<a href="javascript:;"
:class = "{active: index === curIdx}"
>{{text}}</a>
</div>
</template>
<script>
import {getCurrentInstance} from 'vue'
export default {
name: "TabItem",
props: {
text: String,
curIdx: Number
},
setup(props,ctx){
const instance = getCurrentInstance();
const index = instance.vnode.key;
const clickItem = ()=>{
ctx.emit("click-item",index);
}
return {index, clickItem};
}
}
</script>
- 父组件Tabs在渲染的每个TabItem时,已经将key值传入,作为每个Tabs项的Id。通过Composition API “getCurrentInstance”获取当前组件实例,然后即可获取当前tab项的Id,这样做的好处是我们省去了定义其他属性获取Id的步骤。
- 当点击TabItem时候,需要将父组件Tabs的响应式数据CurrentIdx进行修改,显然该步骤显然在TabItem组件中无法完成。
- 因此通过自定义事件click-item,将当前Tabs项的Id通过emit函数传递给父组件Tabs,在Tabs中的clickItem函数中修改CurrentIdx。
- 调用emit函数不同于Vue2.0的this.$emit方式,通过setup的第二个参数,上下文参数对象,调用emit函数。
子组件TabContent关键代码如下:
<template>
<div class="tab-content">
<h1>{{item.title}}</h1>
<p>Sales: {{ item.content}}</p>
</div>
</template>
<script>
import {watch} from 'vue'
export default {
name: "TabContent",
props:{
curIdx:Number,
data:{
type: Array,
default: ()=>[]
},
},
setup(props){
const item = {};
watch(()=>{
return props.curIdx;
},(val)=>{
const {Month,Sales} = props.data.filter(item=>item.Id === val)[0];
item.title = Month;
item.content = Sales;
});
return {item};
}
}
</script>
- TabContent通过属性接收到curIdx,如果该值发生变化,则需要重新查找指定月份的数据
- 因此通过watch函数对curIdx进行监听,该函数包括两个函数参数,第一个函数返回要监听的数据,第二个函数在监听数据发生变化后执行。
- watch函数的使用不同于Vue2.0,该函数也是Composition API,通过对象解构的方式引入。
基于事件代理父子组件通信
父组件Tabs关键代码如下:
<template>
<div>
<div class="Tabs" @click = "clickItem($event)">
<tab-item
v-for = "item in data"
:key = "item.Id"
:text = "item.Month"
:curIdx = "CurrentIdx"
>
</tab-item>
</div>
<tab-content :curIdx = "CurrentIdx" :data = "data"></tab-content>
</div>
</template>
<script>
import TabItem from "./tab-item";
import TabContent from "./tab-content";
import {onMounted, reactive, toRefs} from "vue";
export default {
name: "Tabs",
props:{
data:{
type:Array,
default:()=>[]
},
initialIdx: {
type:Number,
default:1
}
},
components:{
TabItem,
TabContent
},
setup(){
const state = reactive({CurrentIdx:0});
onMounted(()=>{
state.CurrentIdx = props.initialIdx;
});
const clickItem = (e)=>{
state.CurrentIdx = Number(e.target.getAttribute("date-index")) || 1;
};
return {
...toRefs(state),
clickItem
};
}
}
</script>
子组件TabItem关键代码如下:
<template>
<div class="tab-item" :data-index="index">
<a href="javascript:;"
:class = "{active: index === curIdx}"
>{{text}}</a>
</div>
</template>
<script>
import {getCurrentInstance} from 'vue'
export default {
name: "TabItem",
props: {
text: String,
curIdx: Number
},
setup(props,ctx){
const instance = getCurrentInstance();
const index = instance.vnode.key;
return {index};
}
}vuex
</script>
子组件TabContent关键代码同上。
- 通过事件代理的方式获取当前选中Tabs项Id,因此将clickItem方法绑定在父组件Tabs上
- TabItem不再需要绑定任何click事件,只需要将当前Tabs项的Id,绑定到data-index属性上,便于父组件通过事件代理函数的参数获取。
- 在点击TabItem组件后,利用事件冒泡原理,触发Tabs的click方法,通过方法参数,获取当前的Tabs项Id。
- 在点击事件的方法中,将父组件Tabs中的CurrentIdx的值修改为当前点击的Tabs项Id,该Id通过事件代理参数获取。
基于vuex实现父子组件通信
父组件Tabs关键代码如下
<template>
<div>
<div class="Tabs" >
<tab-item
v-for = "item in data"
:key = "item.Id"
:text = "item.Month"
:curIdx = "CurrentIdx"
>
</tab-item>
</div>
<tab-content :curIdx = "CurrentIdx" :data = "data"></tab-content>
</div>
</template>
<script>
import TabItem from "./tab-item";
import TabContent from "./tab-content";
import {onMounted,computed} from "vue";
import {useStore} from "vuex"
export default {
name: "Tabs",
props:{
data:{
type:Array,
default:()=>[]
},
initialIdx: {
type:Number,
default:1
}
},
components:{
TabItem,
TabContent
},
setup(props,ctx){
const store = useStore();
const state = store.state;
onMounted(()=>{
ctx.commit("setCurIdx", props.initialIdx);
});
const CurrentIdx = computed(()=>{
return store.state.curIdx;
})
return {CurrentIdx};
}
}
</script>
子组件TabItem关键代码如下:
<template>
<div class="tab-item" @click ="clickItem()">
<a href="javascript:;"
:class = "{active: index === curIdx}"
>{{text}}</a>
</div>
</template>
<script>
import {useStore} from "vuex"
import {getCurrentInstance} from 'vue'
export default {
name: "TabItem",
props: {
text: String,
curIdx: Number
},
setup(props,ctx){
const store = useStore();
const state = store.state;
const instance = getCurrentInstance();
const index = instance.vnode.key;
const clickItem = ()=>{
state.commit("setCurIdx",index);
}
return {index, clickItem};
}
}
</script>
store/index.js 代码如下:
import {createStore} from "vuex"
export default createStore({
state: {curIdx: 1,},
mutations:{
setCurIdx(state,curIdx){
state.curIdx = curIdx;
}
}
});
- 选中Tabs项的Id不再通过父组件进行管理,而是通过全局store对象进行管理。
- clickItem函数放到TabItem中执行即可,子组件通过调用mutations中的setCurIdx方法,直接修改选中的TabItem的Id。
- 父组件Tabs通过设置计算属性CurrentIdx,实现组件的联动。即无论store中的curIdx在那个组件被修改,父组件立刻就得知该变化。
基于依赖注入的父子组件通信
父组件Tabs关键代码如下:
<template>
<div>
<div class="Tabs">
<tab-item v-for="item in data" :key="item.Id" :text="item.Month" />
</div>
<tab-content :data="data" />
</div>
</template>
<script>
import TabItem from "./item";
import TabContent from "./tabcontent";
import { provide, reactive, toRefs } from "vue";
export default {
name: "TabsDI",
props: {
data: {
type: Array,
default: () => []
},
InitialIdx: { type: Number, default: 1 }
},
components: {
TabItem,
TabContent
},
setup(props) {
const state = reactive({
currentIdx: props.InitialIdx
});
const clickItem = index => {
state.currentIdx = index;
};
const data = toRefs(state);
provide("currentIdx", data.currentIdx);
provide("clickItem", clickItem);
return {
...data
};
}
};
</script>
- 子组件TabItem和TabContent 不再需要通过属性获取当前选中项的Id
- 父组件TabsDI通过provide方法将当前选中项currentIdx 和修改该Id的方法clickItem传递给子组件
- 本例子通过reactive方法创建响应式数据,请注意const data = toRefs(state);这句不可以省略。state.currentIdx只是一个数值,必须通过该方法转换为响应式数据。否则组件无法监控器变化。
子组件TabItem关键代码如下:
<template>
<div class="tab-item" @click="clickItem(index)">
<a href="javascript:;" :class="{ active: curIdx === index }">{{ text }}</a>
</div>
</template>
<script>
import { getCurrentInstance, inject } from "vue";
export default {
name: "TabItem",
props: {
text: String
},
setup() {
const instance = getCurrentInstance();
const index = instance.vnode.key;
const clickItem = inject("clickItem");
const curIdx = inject("currentIdx");
return { curIdx, index, clickItem };
}
};
</script>
- 子组件通过inject方法获取当前选中项的Id
- 子组件通过inject方法获取修改父组件选中项的方法
子组件TabContent关键代码如下:
<template>
<div class="tab-content">
<h1>{{ item.title }}</h1>
<p>Sales: {{ item.content }}</p>
</div>
</template>
<script>
import { inject, computed } from "vue";
export default {
name: "TabContent",
props: {
curIdx: {
type: Number,
default: 0
},
data: {
type: Array,
default: () => []
},
text: String
},
setup(props) {
const val = inject("currentIdx");
const item = computed(() => {
const { Month, Sales } = props.data.filter(
item => item.Id === val.value
)[0];
return {
title: Month,
content: Sales
};
});
return { item };
}
};
</script>
- 通过inject方法获取当前选中项Id
- 因为本例不再使用属性传值的方式获取当前选中项Id,所以不再使用watch方法监控属性变化
- 由于当前选中项Id值currentIdx依然是响应式数据,因此采用computed方法监控器变化。如果变化则更新销量数据
附录
数据结构
export default [
{Id:1,Month:"January",Sales:100000},
{Id:1,Month:"February",Sales:200000},
{Id:1,Month:"March",Sales:300000},
{Id:1,Month:"April",Sales:300000},
{Id:1,Month:"May",Sales:400000},
]
更多推荐
已为社区贡献4条内容
所有评论(0)