VUE3(三十八)Vue3.2子父组件交互(vue、ts分离)

最开始,尝试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" />&nbsp;{{ 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" />&nbsp;{{ 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;
};

到这里,就将上文中的代码分离开了。

有好的建议,请在下方输入你的评论。

相关推荐
滚雪球~40 分钟前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语41 分钟前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport43 分钟前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254881 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript
苹果醋32 小时前
Golang的文件加密工具
运维·vue.js·spring boot·nginx·课程设计