之前在做博客手机端的时候,特意把底部菜单封装成了一个自定义组件,尝试了一下vue3.2语法的子传父、父传子、子调父、父调子。
首先放一下自定义组件的代码:
Menu.vue
<template> <!-- 手机底部菜单(别问我为什么把现成的组件又封了一个自定义组件,问就是为了试试子传父、父传子、子调父、父调子) --> <!-- <van-grid clickable :column-num="4"> <van-grid-item icon="wap-home" text="首页" to="/phone/index" @click="goIndex" :icon-color="homeColor" /> <van-grid-item icon="font" text="杂谈" to="/phone/gossip" @click="goOther" :icon-color="otherColor"/> <van-grid-item icon="comment" text="归档" to="/phone/index" @click="goPlaceFile" :icon-color="placeFileColor"/> <van-grid-item icon="friends" text="我的" to="/phone/index" @click="goPersonal" :icon-color="personalColor" /> </van-grid> --> <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 { reactive, ref, toRefs } 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 router = useRouter(); const route = useRoute(); const data = reactive({ // tabbar 默认选中索引 active: 0, }); /** * @name: 将data绑定值dataRef * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-01-10 */ const { active } = toRefs(data); // 使用defineEmits创建名称,接受一个数组 const $myemit = defineEmits([ "getIndexDataList", "getOtherData", "getPlaceFileData", "getPersonalData", ]); /** * @name: tabbar 发生改变时 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-08-11 * @param: index number 索引 */ 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 分类 */ const getIndexDataList = ( page: number = 1, search: string = "", category_id: number = 0, sign: boolean = true ): void => { // 判断是否为数字 if (parseFloat(page).toString() == "NaN") { page = 1; } // 请求数据 // getUserSessionData(); try { data.active = 0; Toast.loading({ message: "加载中...", forbidClick: true, }); // utils.alertLoadExec(true); let param = { page: page, search: search, category_id: utils.encryptCode({ category_id: category_id }), }; getIndexData(param).then(function (response: any) { response.page = page; response.category_id = category_id; // console.log(response); // 传递数据给父组件 $myemit("getIndexDataList", response); // 自定义loading消失 // utils.alertLoadExec(false); utils.sleep(1000).then(() => { // 这里写sleep之后需要去做的事情 Toast.clear(); }); }); } catch (error) { console.log("chucuole"); // 自定义loading消失 // utils.alertLoadExec(false); Toast.clear(); } }; /** * @name: 获取杂谈页数据 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-08-11 * @param: page number 页码 */ const getOtherData = (sign: Boolean = true): void => { Toast.loading({ message: "加载中...", forbidClick: true, }); // utils.alertLoadExec(true); // 请求数据 // getUserSessionData(); try { data.active = 1; let param = {}; getRemarkList(param).then(function (response: any) { // console.log(response); // 传递数据给父组件 $myemit("getOtherData", response); // 自定义loading消失 // utils.alertLoadExec(false); utils.sleep(1000).then(() => { // 这里写sleep之后需要去做的事情 Toast.clear(); }); }); } catch (error) { console.log("chucuole"); // 自定义loading消失 // utils.alertLoadExec(false); Toast.clear(); } }; /** * @name: 去归档页 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-08-11 */ const getPlaceFileData = (sign: Boolean = true): void => { // utils.alertLoadExec(true); Toast.loading({ message: "加载中...", forbidClick: true, }); // getUserSessionData(); // 请求数据 try { data.active = 2; let param = {}; getFileList(param).then(function (response: any) { // console.log(response); // 传递数据给父组件 $myemit("getPlaceFileData", response); // 自定义loading消失 // utils.alertLoadExec(false); utils.sleep(5000).then(() => { // 这里写sleep之后需要去做的事情 Toast.clear(); }); }); } catch (error) { console.log("chucuole"); // 自定义loading消失 // utils.alertLoadExec(false); Toast.clear(); } }; /** * @name: 去个人中心页 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-08-11 */ const getPersonalData = (): void => { data.active = 3; }; /** * @name: 获取用户登录信息 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-07-18 */ 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("登录信息出错!"); } }; getUserSessionData(); // 将组件中的属性暴露出去,这样父组件可以获取 defineExpose({ ...toRefs(data), getIndexDataList, getOtherData, getPlaceFileData, getPersonalData, }); </script> <style lang="scss"> // @import "../../assets/css/components/pc/Footer.scss"; </style>
父组件调用:
<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="active" 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" /> </div> </template> <script lang="ts" setup> import { PropType, ref, watch, reactive, toRefs, provide, inject, onBeforeMount, // 在组件挂载之前执行的函数 onMounted, nextTick, } from "vue"; // 引入路由 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({ // 文章列表 dataList: Array<any>(), // 分类列表 categoryList: Array<any>(), // 页码 page: <number>(route.query.page ? route.query.page : 1), // 子分类id category_id: <number>(route.query.category_id ? route.query.category_id : 0), // 页码(控制加载下一页是否显示) articlePage: <number>1, // 是否请求 req: <boolean>(route.query.req ? route.query.req : true), // 下拉刷新标识 loading: <boolean>false, // 搜索值 search: <string>"", }); /** * @name: 将data绑定值dataRef * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-01-10 */ const { dataList, categoryList, page, category_id, articlePage, loading, search, } = 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; } // 子组件ref(TypeScript语法)下面这这两种写法也可以,推荐使用Typescript语法 const MenuRef = ref<InstanceType<typeof Menu> & menuData>(); // const MenuRef = ref<InstanceType<typeof Menu>>() // const MenuRef = ref(null) // =============================================================================== // 方法 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 */ const onRefresh = () => { // 执行子组件方法 MenuRef.value?.getIndexDataList(data.page); Toast("刷新成功"); data.loading = false; }; /** * @name: 跳转文章详情页 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-07-18 * @param: category_id number 分类 */ 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 */ const goIndex = (response: any): void => { try { data.page = response.page; data.category_id = response.category_id; if (data.page > 1) { // 数组合并 data.dataList.push(...response.articleShow); } else { // 数组赋值 data.dataList = response.articleShow; } data.categoryList = response.cateList; data.articlePage = response.articlePage; } catch (error) { console.log("出错了"); } }; /** * @name: 获取搜索数据 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-07-18 */ const getSearchData = (): void => { data.page = 1; MenuRef.value?.getIndexDataList(data.page, data.search, data.category_id); }; /** * @name: 加载下一页数据 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-07-18 */ const nextPage = (): void => { data.page += 1; // 调用子组件的方法 MenuRef.value?.getIndexDataList(data.page, data.search, data.category_id); }; /** * @name: 获取分类下的文章列表 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-07-18 */ const onClickTab = (obj: number): void => { data.category_id = obj; data.page = 1; // 调用子组件的方法 MenuRef.value?.getIndexDataList(data.page, "", data.category_id); }; /** * @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()//*/ }); /** * @name: 生命周期---页面挂载完成 * @author: camellia * @email: guanchao_gc@qq.com * @date: 2022-07-18 */ const mounted = onMounted(() => { // console.log('获取子组件中的性别', MenuRef ); // console.log('获取子组件中的其他信息', MenuRef.value?.info ); // console.log('获取子组件中的其他信息', MenuRef.value.homeColor ); // 执行子组件方法 MenuRef.value?.getIndexDataList(data.page); }); </script> <style> em { color: #f73131; } </style> <style scoped lang="scss"> @import "/@/assets/css/phone/index.scss"; </style>
还是最开始说的,组件封的挺鸡肋的,但是尝试了一下字符组件的交互,简单讲:父组件调用子组件的方法,子组件的方法获取数据,再将数据传递回父组件,父组件拿到数据之后再进行页面的数据渲染。
具体使用的方法,请参见VUE3官方文档:cn.vuejs.org/guide/types…
或者,参见掘金VUE3.2子父组件总结文章:juejin.cn/post/700610…
但是,把html代码和ts代码写到一起可能不是一个太好的习惯。我琢磨琢磨怎么把他们分离开。
以上大概就是我自己尝试的VUE3.2语法的子父组件的交互。
有好建议,请在下方是输入你的评论。