最开始,尝试VUE3.2的子父组件调用的时候,我是将html代码和TS代码都写在了一个文件中,这样写比较简单,但是在后期维护的时候可能会比较麻烦,还是分文件写,html代码就放到VUE文件中,TS代码就放到TS中,这样最好。
那首先,我们先来改造我们的自定义组件menu
改造前的代码参照:《VUE3(三十七)Vue3.2子父组件交互(vue、ts不分离)》
这里先放一下改造后的代码:
Menu.vue
<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
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
<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下引入子组件:
// 引入子组件 import Menu from "/@/components/phone/Menu.vue";
2:子组件实例需要在函数中return回来:
// ================================================================== // 调用生命周期函数 const { MenuRef } = Mounted();
父组件:index.ts
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来编写程序,先定义了一个接口,确定组件类型:
/** * @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文件:
/** * @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方法的。
/** * @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; };
到这里,就将上文中的代码分离开了。
有好的建议,请在下方输入你的评论。