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语法的子父组件的交互。

 


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


目录
相关文章
|
3天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
3天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
3天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
2天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
2天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
17天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
3天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
4天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
4天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
9天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发