最开始,尝试VUE3.2的子父组件调用的时候,我是将html代码和TS代码都写在了一个文件中,这样写比较简单,但是在后期维护的时候可能会比较麻烦,还是分文件写,html代码就放到VUE文件中,TS代码就放到TS中,这样最好。
那首先,我们先来改造我们的自定义组件menu
改造前的代码参照:《VUE3(三十七)Vue3.2子父组件交互(vue、ts不分离)》
这里先放一下改造后的代码:
Menu.vue
xml
<template>
<!-- 手机底部菜单(别问我为什么把现成的组件又封了一个自定义组件,问就是为了试试子传父、父传子、子调父、父调子) -->
<van-tabbar
v-model="active"
@change="onChange"
active-color="#24657D"
inactive-color="#323233"
>
<van-tabbar-item icon="wap-home" to="/phone/index">首页</van-tabbar-item>
<van-tabbar-item icon="font" to="/phone/gossip">杂谈</van-tabbar-item>
<van-tabbar-item icon="comment" to="/phone/placeFile">归档</van-tabbar-item>
<van-tabbar-item icon="friends" to="/phone/personal">我的</van-tabbar-item>
</van-tabbar>
</template>
<script lang="ts" setup>
import { defineExpose, defineProps } from "vue";
import {
// 这里是方法
Mounted,
getIndexDataList,
getOtherData,
getPlaceFileData,
getPersonalData,
getUserSessionData,
onChange,
test,
// 这里是变量
active,
} from "/@/assets/js/components/phone/Menu";
// ====================================================
// 将组件中的属性暴露出去,这样父组件可以调用
defineExpose({
getIndexDataList,
getOtherData,
getPlaceFileData,
getPersonalData,
});
// ====================================================
// 接受父组件 传递来的参数
const props = defineProps({
activeDefine: {
type: String,
default: "",
},
});
// 父组件传递来的参数赋值 子组件中的参数
active.value = parseInt(props.activeDefine);
console.log(props);
// ====================================================
// 测试参数传递
test(props.activeDefine);
// ====================================================
// 调用生命周期函数
Mounted();
</script>
上方的代码中,我们需要注意以下几个问题:
1:defineExpose、defineProps 两个函数必须在setup下调用才可以。
2:defineExpose是用来暴露子组件参数及方法的。
3:defineProps是用来接受父组件传递给子组件参数的。
4:父组件传递过来的参数,可以通过函数传参调用传递在TS文件的方法中处理。
我们在来看一下组件对应的TS文件:
Menu.ts
typescript
import { reactive, ref, toRefs,onMounted,defineEmits,defineExpose,defineProps } from "vue";
// 引入axios钩子
import axios from "/@/request/axios";
// 引入路由
import { useRouter, useRoute } from "vue-router";
// 引入公共js文件
import utils from "/@/assets/js/public/function";
// api 接口文件
import {
getIndexData,
getRemarkList,
getFileList,
getUserSession,
} from "/@/api/phone/index";
import { common, userinfo } from "/@/hooks/common";
import { Toast } from "vant";
const data = reactive({
// tabbar 默认选中索引
active: 0,
});
/**
* @name: 将data绑定值dataRef
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-01-10
*/
export const { active } = toRefs(data);
/**
* @name: 调用生命周期onMounted
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2023-01-10
*/
export const Mounted = () => {
onMounted( ()=>{
// =============================================
// 初始调用
getUserSessionData();
});
}
/**
* @name: tabbar 发生改变时
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-08-11
* @param: index number 索引
*/
export const onChange = (index:any): void => { };
/**
* @name: 获取首页数据公共方法
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-08-11
* @param: page number 页码
* @param: search string 搜索值
* @param: category_id number 分类
*/
export const getIndexDataList = async(
page: number = 1,
search: string = "",
category_id: number = 0,
sign: boolean = true
): Promise<any> => {
const dataLists = ref<any>();
const actives = ref<number>(0);
// 判断是否为数字
if (parseFloat(page).toString() == "NaN") {
page = 1;
}
// 请求数据
// getUserSessionData();
try {
Toast.loading({
message: "加载中...",
forbidClick: true,
});
// utils.alertLoadExec(true);
let param = {
page: page,
search: search,
category_id: utils.encryptCode({ category_id: category_id }),
};
await getIndexData(param).then(function (response: any) {
response.page = page;
response.category_id = category_id;
// console.log(response);
// 传递数据给父组件
// $myemit("getIndexDataList", response);
dataLists.value = response;
utils.sleep(1500).then(() => {
// 这里写sleep之后需要去做的事情
Toast.clear();
});
});
} catch (error) {
console.log("chucuole");
// 自定义loading消失
// utils.alertLoadExec(false);
Toast.clear();
}
actives.value = 0;
// 返回参数
return {dataLists,actives}
};
/**
* @name: 获取杂谈页数据
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-08-11
* @param: page number 页码
*/
export const getOtherData = async(sign: Boolean = true): Promise<any> => {
Toast.loading({
message: "加载中...",
forbidClick: true,
});
const result = ref<any>();
const actives = ref<number>(0);
try {
let param = {};
await getRemarkList(param).then(function (response: any) {
// 传递数据给父组件
// $myemit("getOtherData", response);
result.value = response;
utils.sleep(1500).then(() => {
// 这里写sleep之后需要去做的事情
Toast.clear();
});
});
} catch (error) {
console.log("chucuole");
Toast.clear();
}
actives.value = 1;
// 返回参数
return {result,actives};
};
/**
* @name: 去归档页
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-08-11
*/
export const getPlaceFileData = async(sign: Boolean = true): Promise<any> => {
// utils.alertLoadExec(true);
Toast.loading({
message: "加载中...",
forbidClick: true,
});
const result = ref<any>();
const actives = ref<number>(0);
// 请求数据
try {
let param = {};
await getFileList(param).then(function (response: any) {
// 传递数据给父组件
// $myemit("getPlaceFileData", response);
result.value = response;
utils.sleep(5000).then(() => {
// 这里写sleep之后需要去做的事情
Toast.clear();
});
});
} catch (error) {
console.log("chucuole");
Toast.clear();
}
actives.value = 2;
// 返回参数
return {result,actives};
};
/**
* @name: 去个人中心页
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-08-11
*/
export const getPersonalData = (): any => {
// data.active = 3;
const actives = ref<number>(0);
actives.value = 3;
// 返回参数
return {actives};
};
/**
* @name: 获取用户登录信息
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const getUserSessionData = (): void => {
try {
let param = {};
getUserSession(param).then(function (response: any) {
userinfo.email = response.email;
userinfo.figureurl = response.figureurl;
userinfo.nickname = response.nickname;
userinfo.userid = response.userid;
});
} catch (error) {
console.log("登录信息出错!");
}
};
/**
* @name: 测试参数传递
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const test = (val:any) => {
console.log(val)
}
这个原理其实跟后端语言的原理很像,就是调用函数,然后接受函数的返回值,我这里的调用原理是,父组件调用子组件的方法,子组件的方法请求数据库,再将获取到的数据返回给父组件,父组件拿到数据之后,进行页面渲染。基本原理就是这个样子。
需要注意,数据请求是Promise异步请求,我们需要使用async和await来将数据赋值给我们定义的变量(详细使用参考上方代码)。
剩下的代码其实就没什么可说的了,将获取到的数据赋值到ref绑定的变量中,return就可以了(也可以绑定到普通变量上)
下边,我们来说一下父组件是如何调用子组件的。
父组件:index.vue
xml
<template>
<!-- header -->
<div class="header">
<van-nav-bar title="时间里的" />
<!-- 搜索框 -->
<van-search
v-model="search"
show-action
placeholder="请输入搜索关键词(本站采用sphinx搜索引擎)"
>
<template #action>
<div @click="getSearchData">搜索</div>
</template>
</van-search>
<!-- <van-search v-model="search" placeholder="请输入搜索关键词(本站采用sphinx搜索引擎)" clearable @click-left-icon="getSearchData" /> -->
</div>
<!-- body -->
<!-- <van-pull-refresh v-model="loading" @refresh="onRefresh"> -->
<div class="body">
<van-tabs v-model:active="menuActive" animated @click-tab="onClickTab">
<!-- 全部 -->
<van-tab title="全部" name="0">
<div
class="article"
v-for="(item, index) in dataList"
:key="index"
@click="jumpArticleDetail(item.id)"
>
<div class="title" v-html="item.arttitle"></div>
<div class="desc">
<div class="left-div">
<div class="author">{{ item.another }} | {{ item.putime }}</div>
<div class="desc-show" v-html="item.artdesc"></div>
</div>
<div class="right-div">
<img :src="item.artlogo" style="width: 100%" class="lazyload" />
</div>
</div>
<div class="label">
<div class="label-left">
<van-icon name="browsing-history-o" /> {{ item.click_num }}
</div>
<div class="label-right">
{{ item.labelStr }}
</div>
</div>
</div>
<div class="next-page" @click="nextPage" v-if="articlePage > page">
点击加载下一页
</div>
</van-tab>
<!-- 循环 -->
<van-tab
v-for="(it, ind) in categoryList"
:key="ind"
:title="it.cat_name"
:name="it.id"
>
<div
class="article"
v-for="(item, index) in dataList"
:key="index"
@click="jumpArticleDetail(item.id)"
>
<div class="title" v-html="item.arttitle"></div>
<div class="desc">
<div class="left-div">
<div class="author">{{ item.another }} | {{ item.putime }}</div>
<div class="desc-show" v-html="item.artdesc"></div>
</div>
<div class="right-div">
<img :src="item.artlogo" style="width: 100%" class="lazyload" />
</div>
</div>
<div class="label">
<div class="label-left">
<van-icon name="browsing-history-o" /> {{ item.click_num }}
</div>
<div class="label-right">
{{ item.labelStr }}
</div>
</div>
</div>
<div class="next-page" @click="nextPage" v-if="articlePage > page">
点击加载下一页
</div>
</van-tab>
</van-tabs>
</div>
<img
src="https://resource.guanchao.site/uploads/gotop/timg.png"
class="go_top"
@click="goToTop"
/>
<!-- </van-pull-refresh> -->
<!-- navbar -->
<div class="footer">
<!-- <Menu @getIndexDataList="goIndex" ref="MenuRef" /> -->
<Menu @getIndexDataList="goIndex" ref="MenuRef" activeDefine="0" />
</div>
</template>
<script lang="ts" setup>
// import { ref, onMounted } from "vue";
// 引入子组件
import Menu from "/@/components/phone/Menu.vue";
import {
// 这里是方法
goToTop,
jumpArticleDetail,
goIndex,
getSearchData,
nextPage,
onClickTab,
Mounted,
// 这里是变量
search,
page,
dataList,
categoryList,
articlePage,
loading,
menuActive,
} from "/@/assets/js/phone/index";
// ==================================================================
// 调用生命周期函数
const { MenuRef } = Mounted();
</script>
上方代码,需要注意一下几点:
1:必须在setup下引入子组件:
javascript
// 引入子组件
import Menu from "/@/components/phone/Menu.vue";
2:子组件实例需要在函数中return回来:
scss
// ==================================================================
// 调用生命周期函数
const { MenuRef } = Mounted();
父组件:index.ts
ini
import {
PropType,
ref,
watch,
reactive,
toRefs,
provide,
inject,
onBeforeMount, // 在组件挂载之前执行的函数
onMounted,
nextTick,
} from "vue";
// 引入路由
import router from "/@/router";
// 引入路由
import { useRouter, useRoute } from "vue-router";
// 引入公共js文件
import utils from "/@/assets/js/public/function";
// 引入子组件
import Menu from "/@/components/phone/Menu.vue";
import { common, userinfo } from "/@/hooks/common";
import { Toast } from "vant";
// =============================================================================
// 实例化路由
// const router = useRouter();
// const route = useRoute();
/**
* @name: 声明data
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-01-18
*/
const data = reactive({});
/**
* @name: 将data绑定值dataRef
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-01-10
*/
export const {} = toRefs(data);
// ===============================================================================
// 子组件相关
/**
* @name: 定义子组件暴露给父组件的值属性列表
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
interface menuData {
goIndex: () => void;
getIndexDataList: () => void;
homeColor: string;
otherColor: string;
placeFileColor: string;
personalColor: string;
}
// ===============================================================================
// 方法
// 页码
export let page = ref<any>(0);
// 子分类id
export let category_id = ref<any>(0);
// 是否请求
export let req = ref<any>(true);
// 文章列表
export let dataList = ref(Array<any>());
// 分类列表
export let categoryList = ref(Array<any>());
// 页码(控制加载下一页是否显示)
export let articlePage = ref(<number>1);
// 下拉刷新标识
export let loading = ref(<boolean>false);
// 搜索值
export let search = ref(<string>"");
// 首页分类菜单默认选中
export let menuActive = ref<number>(0);
// 底部菜单
export let active = ref<number>(0);
// ===============================================================================
// 子组件ref(TypeScript语法)下面这这两种写法也可以,推荐使用Typescript语法
// 子组件menu的对象
export const MenuRef = ref<InstanceType<typeof Menu> & menuData>();
// const MenuRef = ref<InstanceType<typeof Menu>>()
// const MenuRef = ref(null)
/**
* @name: 调用生命周期onMounted
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2023-01-10
*/
export const Mounted = () => {
onMounted( async()=>{
const route = useRoute();
page.value = route.query.page ?? 1;
category_id.value = route.query.category_id ?? 0;
req.value = route.query.req ?? true;
// MenuRef.value = ref<InstanceType<typeof Menu> & menuData>();
// console.log('获取子组件中的性别', MenuRef.value );
// console.log('获取子组件中的其他信息', MenuRef.value?.info );
// console.log('获取子组件中的其他信息', MenuRef.value?.homeColor );
// 执行子组件方法
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value);
// console.log(dataLists.value.articleShow);
// console.log(actives.value);
dataList.value = dataLists.value.articleShow;
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
});
return {MenuRef};
}
/**
* @name:滚动条回顶部
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const goToTop = () => {
// 滚动条回顶部
let json = document.getElementById("body");
if (json != null)
{
json.scrollTop = 0;
}
};
/**
* @name: 下拉刷新方法
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const onRefresh = async():Promise<void> => {
let result:any;
const categoryList = ref(Array<any>());
const articlePage = ref(Array<any>());
const active = ref<number>(0);
// 执行子组件方法
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value);
// console.log(result);
dataList.value = dataLists.value.articleShow;
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
Toast("刷新成功");
loading.value = false;
};
/**
* @name: 跳转文章详情页
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
* @param: category_id number 分类
*/
export const jumpArticleDetail = (article_id: Number) => {
router.push({
path: "/phone/articleDetail",
query: {
article_id: utils.encryptCode({ article_id: article_id }),
path: "index",
},
});
};
/**
* @name: 子组件向父组件抛出的方法
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const goIndex = (response: any): void => {
try
{
page.value = response.page;
category_id.value = response.category_id;
if (page.value > 1)
{
// 数组合并
dataList.value.push(...response.articleShow);
}
else
{
// 数组赋值
dataList.value = response.articleShow;
}
categoryList.value = response.cateList;
articlePage.value = response.articlePage;
}
catch (error)
{
console.log("出错了");
}
};
/**
* @name: 获取搜索数据
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const getSearchData = async():Promise<void> => {
page.value = 1;
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value, search.value, category_id.value);
dataList.value = dataLists.value.articleShow;
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
};
/**
* @name: 加载下一页数据
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const nextPage = async() => {
page.value += 1;
// 调用子组件的方法
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value, search.value, category_id.value);
// 循环
dataLists.value.articleShow.forEach((item:any) => {
dataList.value.push(item);
});
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
};
/**
* @name: 获取分类下的文章列表
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const onClickTab = async(obj: number) => {
const categoryList = ref(Array<any>());
const articlePage = ref(Array<any>());
const active = ref<number>(0);
category_id.value = obj;
page.value = 1;
// 调用子组件的方法
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value, "", category_id.value);
dataList.value = dataLists.value.articleShow;
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
return {categoryList,articlePage,active};
};
/**
* @name: nextTick 页面发生变化即渲染
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
nextTick(() => {
/*// 获取子组件name
console.log(MenuRef._value)
console.log(MenuRef.__v_isRef)
console.log(MenuRef.__v_isShallow)
console.log(MenuRef._rawValue)
console.log(MenuRef.value.count)
// 加个问号这种写法,没有属性也不会报错
console.log(MenuRef.value?.homeColor)
// 执行子组件方法
MenuRef.value.goIndex()//*/
});
关于上方代码,我们需要注意以下几点:
1:我使用TS来编写程序,先定义了一个接口,确定组件类型:
typescript
/**
* @name: 定义子组件暴露给父组件的值属性列表
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
interface menuData {
goIndex: () => void;
getIndexDataList: () => void;
homeColor: string;
otherColor: string;
placeFileColor: string;
personalColor: string;
}
2:在生命周期onMounted函数中将子组件实例化的对象返给index.vue文件:
ini
/**
* @name: 调用生命周期onMounted
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2023-01-10
*/
export const Mounted = () => {
onMounted( async()=>{
const route = useRoute();
page.value = route.query.page ?? 1;
category_id.value = route.query.category_id ?? 0;
req.value = route.query.req ?? true;
// 执行子组件方法
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value);
dataList.value = dataLists.value.articleShow;
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
});
return {MenuRef};
}
3:同样,在调用子组件的方法的时候,还是需要使用async和await方法的。
ini
/**
* @name: 下拉刷新方法
* @author: camellia
* @email: guanchao_gc@qq.com
* @date: 2022-07-18
*/
export const onRefresh = async():Promise<void> => {
let result:any;
// 执行子组件方法
const {dataLists,actives} = await MenuRef.value?.getIndexDataList(page.value);
// console.log(result);
dataList.value = dataLists.value.articleShow;
categoryList.value = dataLists.value.cateList;
articlePage.value = dataLists.value.articlePage;
active.value = actives.value;
Toast("刷新成功");
loading.value = false;
};
到这里,就将上文中的代码分离开了。
有好的建议,请在下方输入你的评论。