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

 


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


目录
相关文章
|
1天前
|
JavaScript 前端开发 算法
高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图
mermaid是一款非常优秀的基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。非常适合新手学习或者做一些弱交互且自定义要求不高的图表 除了流程图以外,mermaid还支持序列图、类图、状态图、实体关系图等图表可供探索。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1天前
|
JavaScript 前端开发 API
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
onMounted作为vue3中最常用的钩子函数之一,能够灵活、随心应手的使用是每个Vue开发者的必修课,同时根据其不同写法的特性,来选择最合适最有利于维护的写法。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1天前
|
数据采集 资源调度 JavaScript
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
本文介绍了使用 Vue Flow 绘制流程图的方法与技巧。Vue Flow 是一个灵活强大的工具,适合自定义复杂的流程图。文章从环境要求(Node.js v20+ 和 Vue 3.3+)、基础入门案例、自定义功能(节点与连线的定制、事件处理)到实际案例全面解析其用法。重点强调了 Vue Flow 的高度灵活性,虽然预定义内容较少,但提供了丰富的 API 支持深度定制。同时,文中还分享了关于句柄(handles)的使用方法,以及如何解决官网复杂案例无法运行的问题。最后通过对比 mermaid,总结 Vue Flow 更适合需要高度自定义和复杂需求的场景,并附带多个相关技术博客链接供进一步学习。
|
11天前
|
资源调度 JavaScript 前端开发
Pinia 如何在 Vue 3 项目中进行安装和配置?
Pinia 如何在 Vue 3 项目中进行安装和配置?
|
11天前
|
JavaScript 前端开发 算法
vue渲染页面的原理
vue渲染页面的原理
|
1天前
|
JavaScript 前端开发 API
管理数据必备;侦听器watch用法详解,vue2与vue3中watch的变化与差异
一篇文章同时搞定Vue2和Vue3的侦听器,是不是很棒?不要忘了Vue3中多了一个可选项watchEffect噢。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1天前
|
存储 数据采集 供应链
属性描述符初探——Vue实现数据劫持的基础
属性描述符还有很多内容可以挖掘,比如defineProperty与Proxy的区别,比如vue2与vue3实现数据劫持的方式有什么不同,实现效果有哪些差异等,这篇博文只是入门,以后有时间再深挖。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
1月前
|
JavaScript 前端开发 开发者
Vue中的class和style绑定
在 Vue 中,class 和 style 绑定是基于数据驱动视图的强大功能。通过 class 绑定,可以动态更新元素的 class 属性,支持对象和数组语法,适用于普通元素和组件。style 绑定则允许以对象或数组形式动态设置内联样式,Vue 会根据数据变化自动更新 DOM。
|
3月前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
168 1
|
1月前
|
移动开发 JavaScript API
Vue Router 核心原理
Vue Router 是 Vue.js 的官方路由管理器,用于实现单页面应用(SPA)的路由功能。其核心原理包括路由配置、监听浏览器事件和组件渲染等。通过定义路径与组件的映射关系,Vue Router 将用户访问的路径与对应的组件关联,支持哈希和历史模式监听 URL 变化,确保页面导航时正确渲染组件。

热门文章

最新文章