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

简介: VUE3(三十七)Vue3.2子父组件交互(vue、ts不分离)~

之前在做博客手机端的时候,特意把底部菜单封装成了一个自定义组件,尝试了一下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" />&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" />
  </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语法的子父组件的交互。

 


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


目录
相关文章
|
10月前
|
JavaScript 前端开发 安全
Vue 3
Vue 3以组合式API、Proxy响应式系统和全面TypeScript支持,重构前端开发范式。性能优化与生态协同并进,兼顾易用性与工程化,引领Web开发迈向高效、可维护的新纪元。(238字)
1201 139
|
缓存 JavaScript PHP
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
1454 5
|
10月前
|
缓存 JavaScript 算法
Vue 3性能优化
Vue 3 通过 Proxy 和编译优化提升性能,但仍需遵循最佳实践。合理使用 v-if、key、computed,避免深度监听,利用懒加载与虚拟列表,结合打包优化,方可充分发挥其性能优势。(239字)
621 1
|
11月前
|
开发工具 iOS开发 MacOS
基于Vite7.1+Vue3+Pinia3+ArcoDesign网页版webos后台模板
最新版研发vite7+vue3.5+pinia3+arco-design仿macos/windows风格网页版OS系统Vite-Vue3-WebOS。
1095 11
|
10月前
|
JavaScript 安全
vue3使用ts传参教程
Vue 3结合TypeScript实现组件传参,提升类型安全与开发效率。涵盖Props、Emits、v-model双向绑定及useAttrs透传属性,建议明确声明类型,保障代码质量。
690 0
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
1125 1
|
缓存 JavaScript UED
除了循环引用,Vue3还有哪些常见的性能优化技巧?
除了循环引用,Vue3还有哪些常见的性能优化技巧?
569 0
|
JavaScript
vue3循环引用自已实现
当渲染大量数据列表时,使用虚拟列表只渲染可视区域的内容,显著减少 DOM 节点数量。
275 0
|
JavaScript API 容器
Vue 3 中的 nextTick 使用详解与实战案例
Vue 3 中的 nextTick 使用详解与实战案例 在 Vue 3 的日常开发中,我们经常需要在数据变化后等待 DOM 更新完成再执行某些操作。此时,nextTick 就成了一个不可或缺的工具。本文将介绍 nextTick 的基本用法,并通过三个实战案例,展示它在表单验证、弹窗动画、自动聚焦等场景中的实际应用。
1286 17
|
JavaScript 前端开发 算法
Vue 3 和 Vue 2 的区别及优点
Vue 3 和 Vue 2 的区别及优点