之前在做博客手机端的时候,特意把底部菜单封装成了一个自定义组件,尝试了一下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语法的子父组件的交互。
有好建议,请在下方是输入你的评论。