vue3中使用defineComponent封装hook实现模板复用

文章目录

  • 一、前言
  • 二、`useTemplate` 实现
  • 三、最后

一、前言

最近在做 Vue3 项目的时候,在思考一个小问题,其实是每个人都做过的一个场景,很简单,看下方代码

<template><div><div v-for="item in data" :key="item.age"><span>{[ item.name }}</span><span>今年已经 </span><span>{[ item.age }}</span><span>岁啦</span></div></div>
</template>
<script setup lang="ts">
const data = new Array(20).fill(0).map((_, index) => ({name:`sunshine-${index}`,age: index
}));
</script>

其实就是一个普通的不能再普通的循环遍历渲染的案例,咱们往下接着看,如果这样的遍历在同一个组件里出现了很多次,比如下方代码:

<template><div><div v-for="item in data" :key="item.age"><span>{[ item.name }}</span><span>今年已经 </span><span>{[ item.age }}</span><span>岁啦</span></div><!-- 多处使用 --><div v-for="item in data" :key="item.age"><span>{[ item.name }}</span><span>今年已经 </span><span>{[ item.age }}</span><span>岁啦</span></div><!-- 多处使用 --><div v-for="item in data" :key="item.age"><span>{[ item.name }}</span><span>今年已经 </span><span>{[ item.age }}</span><span>岁啦</span></div><!-- 多处使用 --><div v-for="item in data" :key="item.age"><span>{[ item.name }}</span><span>今年已经 </span><span>{[ item.age }}</span><span>岁啦</span></div></div>
</template>
<script setup lang="ts">
const data = new Array(20).fill(0).map((_, index) => ({name:`sunshine-${index}`,age: index
}));
</script>

这个时候我们应该咋办呢?诶!很多人很快就能想出来了,那就是把循环的项抽取出来成一个组件,这样就能减少很多代码量了,比如我抽取成 Item.vue 这个组件:

<!-- Item.vue -->
<template>
<div><span>{[ name }}</span><span>今年已经 </span><span>{[ age }}</span><span>岁啦</span>
</div>
</template>
<script setup lang="ts">
defineProps({name: {type: String,default: ''},age: {type: Numberdefault: 0}
})
</script>

然后直接可以引用并使用它,这样大大减少了代码量,并且统一管理,提高代码可维护性!!!

<template><div><Item v-for="item in data" :key="item.age" :age="item.age" :name="item.name" /></div>
</template>
<script setup lang="ts">
import Item from './Item.vue';
const data = new Array(20).fill(0).map((_, index) => ({name:`sunshine-${index}`,age: index
}));
</script>

但是我事后越想越难受,就一个这么丁点代码量的我都得抽取成组件,那我不敢想象以后我的项目组件数会多到什么地步,而且组件粒度太细,确实也增加了后面开发者的负担~

那么有没有办法,可以不抽取成组件呢?我可以在当前组件里去提取吗,而不需要去重新定义一个组件呢?例如下面的效果

<template>
<div><!-- 在本组件中定义一个复用模板 --><DefineTemplate v-slot="[ age, name }"><div><span>{{ name }}</span><span>今年已经 </span><span>{{ age }}</span><span>岁啦</span></div></DefineTemplate><!-- 可多次使用复用模板 --><ReuseTemplate v-for="item in data" :key="item.age" :age-"item.age" :name-"item.name" /><ReuseTemplate v-for="item in data" :key="item.age" :age-"item.age" :name-"item.name" /><ReuseTemplate v-for="item in data" :key="item.age" :age-"item.age" :name-"item.name" />
</div>
</template>
<script setup lang="ts">import { useTemplate } from '@/hooks/useTemplate';const data = new Array(20).fill(0).map((_, index) => ({name:`sunshine-${index}`,age: index}));const [DefineTemplate, ReuseTemplate] = useTemplate();
</script>

二、useTemplate 实现

想到这,马上行动起来,需要封装一个 useTemplate来实现这个功能

import { defineComponent, shallowRef } from 'vue';import { camelCase } from 'lodash';
import type { Slot } from 'vue';// 将横线命名转大小驼峰
function keysToCamelKebabCase(obj: Record<string, any>) {const newObj: typeof obj = {};for (const key in obj) newObj[camelCase(key)] = obj[key];return newObj;
}export const useTemplate => {const render = shallowRef<Slot | undefined>();const define = defineComponent({setup(_, { slots }) {return () => {// 将复用模板的渲染函数内容保存起来render.value = slots.default;};},});const reuse = defineComponent({setup(_, { attrs, slots }) {return () => {// 还没定义复用模板,则抛出错误if (!render.value) {throw new Error('你还没定义复用模板呢!');}// 执行渲染函数,传入 attrs、slotsconst vnode = render.value({ ...keysToCamelKebabCase(attrs), $slots: slots });return vnode.length === 1 ? vnode[0] : vnode;};},});return [define, reuse];
};

尽管做到这个地步,我还是觉得用的不爽,因为没有类型提示

img

img

我们想要的是比较爽的使用,那肯定得把类型的提示给支持上啊!!!于是给 useTemplate 加上泛型!!加上之后就有类型提示啦~~~~

img

img

加上泛型后的 useTemplate 代码如下:

import { defineComponent, shallowRef } from 'vue';import { camelCase } from 'lodash';
import type { DefineComponent, Slot } from 'vue';// 将横线命名转大小驼峰
function keysToCamelKebabCase(obj: Record<string, any>) {const newObj: typeof obj = {};for (const key in obj) newObj[camelCase(key)] = obj[key];return newObj;
}export type DefineTemplateComponent<Bindings extends object,Slots extends Record<string, Slot | undefined>,
> = DefineComponent<object> & {new (): { $slots: { default(_: Bindings & { $slots: Slots }): any } };
};export type ReuseTemplateComponent<Bindings extends object,Slots extends Record<string, Slot | undefined>,
> = DefineComponent<Bindings> & {new (): { $slots: Slots };
};export type ReusableTemplatePair<Bindings extends object,Slots extends Record<string, Slot | undefined>,
> = [DefineTemplateComponent<Bindings, Slots>, ReuseTemplateComponent<Bindings, Slots>];export const useTemplate = <Bindings extends object,Slots extends Record<string, Slot | undefined> = Record<string, Slot | undefined>,
>(): ReusableTemplatePair<Bindings, Slots> => {const render = shallowRef<Slot | undefined>();const define = defineComponent({setup(_, { slots }) {return () => {// 将复用模板的渲染函数内容保存起来render.value = slots.default;};},}) as DefineTemplateComponent<Bindings, Slots>;const reuse = defineComponent({setup(_, { attrs, slots }) {return () => {// 还没定义复用模板,则抛出错误if (!render.value) {throw new Error('你还没定义复用模板呢!');}// 执行渲染函数,传入 attrs、slotsconst vnode = render.value({ ...keysToCamelKebabCase(attrs), $slots: slots });return vnode.length === 1 ? vnode[0] : vnode;};},}) as ReuseTemplateComponent<Bindings, Slots>;return [define, reuse];
};

三、最后

本人每篇文章都是一字一句码出来,希望对大家有所帮助,多提提意见。顺手来个三连击,点赞👍收藏💖关注✨,一起加油☕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/226359.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Eclipse安装Jrebel eclipse免重启加载项目

每次修改JAVA文件都需要重新启动项目&#xff0c;加载时间太长&#xff0c;eclipse安装jrebel控件,避免重启项目节省时间。 1、Help->Eclipse Marketplace 2、搜索jrebel 3、Help->jrebel->Configuration 配置jrebel 4、激活jrebel 5、在红色框中填入 http://jrebel…

HCIA-Datacom题库(自己整理分类的)——OSPF协议判断

1.路由表中某条路由信息的Proto为OSPF则此路由的优先级一定为10。√ 2.如果网络管理员没有配置骨干区域,则路由器会自动创建骨干区域&#xff1f; 路由表中某条路由信息的Proto为OSPF&#xff0c;则此路由的优先级一定为10。 当两台OSPF路由器形成2-WAY邻居关系时&#xff0…

python-39-flask+nginx+Gunicorn的组合应用

flask nginx Gunicorn 王炸 1 flasknginxgunicornsupervisor 1.1 myapp.py from flask import Flask app Flask(__name__)app.route("/") def test_link():return "the link is very good"if __name__"__main__":app.run()默认是5000端口…

Java开发框架和中间件面试题(10)

目录 104.怎么保证缓存和数据库数据的一致性&#xff1f; 105.什么是缓存穿透&#xff0c;什么是缓存雪崩&#xff1f;怎么解决&#xff1f; 106.如何对数据库进行优化&#xff1f; 107.使用索引时有哪些原则&#xff1f; 108.存储过程如何进行优化&#xff1f; 109.说说…

听GPT 讲Rust源代码--src/tools(29)

File: rust/src/tools/clippy/clippy_lints/src/unused_peekable.rs 在Rust源代码中&#xff0c;rust/src/tools/clippy/clippy_lints/src/unused_peekable.rs这个文件是Clippy工具中一个特定的Lint规则的实现文件&#xff0c;用于检测未使用的Peekable迭代器。 Peekable迭代器…

[BUG] Hadoop-3.3.4集群yarn管理页面子队列不显示任务

1.问题描述 使用yarn调度任务时&#xff0c;在CapacityScheduler页面上单击叶队列&#xff08;或子队列&#xff09;时&#xff0c;不会显示应用程序任务信息&#xff0c;root队列可以显示任务。此外&#xff0c;FairScheduler页面是正常的。 No matching records found2.原…

Unreal Engine游戏引擎的优势

在现在这个繁荣的游戏开发行业中&#xff0c;选择合适的游戏引擎是非常重要的。其中&#xff0c;Unreal Engine作为一款功能强大的游戏引擎&#xff0c;在业界广受赞誉。那Unreal Engine游戏引擎究竟有哪些优势&#xff0c;带大家简单的了解一下。 图形渲染技术 Unreal Engin…

【计算机网络实验】educoder实验八 IPV6网络及其路由 头歌

第一关 IPV6网络基础 //千万不要破坏文档原有结构与内容&#xff01;&#xff01;&#xff01; //以下均为判断题&#xff0c;F&#xff1a;表示错误&#xff0c;T&#xff1a;表示正确 //答案必须写在相应行末尾括号内&#xff0c;F与T二选一&#xff0c;大写 // 1、ipv6协议…

Flink1.17实战教程(第七篇:Flink SQL)

系列文章目录 Flink1.17实战教程&#xff08;第一篇&#xff1a;概念、部署、架构&#xff09; Flink1.17实战教程&#xff08;第二篇&#xff1a;DataStream API&#xff09; Flink1.17实战教程&#xff08;第三篇&#xff1a;时间和窗口&#xff09; Flink1.17实战教程&…

Azure 学习总结

文章目录 1. Azure Function1.1 Azure Function 概念1.2 Azure Function 实现原理1.3 Azure Function 本地调试1.4 Azure Function 云部署 2. Azure API Managment 概念 以及使用2.1 Azure API 概念2.2 Azure API 基本使用 3. Service Bus 应用场景及相关特性3.1 Service Bus 基…

django之drf框架(排序、过滤、分页、异常处理)

排序 排序的快速使用 1.必须是继承GenericAPIView及其子类才能是用排序 导入OrderingFilter类&#xff0c;from rest_framework.filters import OrderingFilter 2.在类中配置类属性 filter_backends[OrderingFilter] 3.类中写属性 ordering_fields [price,id] # 必须是表的…

【论文阅读】Realtime multi-person 2d pose estimation using part affinity fields

OpenPose&#xff1a;使用PAF的实时多人2D姿势估计。 code&#xff1a;GitHub - ZheC/Realtime_Multi-Person_Pose_Estimation: Code repo for realtime multi-person pose estimation in CVPR17 (Oral) paper&#xff1a;[1611.08050] Realtime Multi-Person 2D Pose Estima…

Docker安装Grafana

1. 介绍 Grafana 是一个开源的度量分析和可视化工具&#xff0c;可以通过将采集的数据分析、查询&#xff0c;然后进行可视化的展示&#xff0c;并能实现报警。参考官网地址&#xff1a;Run Grafana Docker image | Grafana documentation 2. 安装Grafana (1) . 下载 命令&…

中北大学 软件构造 U+及上课代码详解

作业1 1.数据类型可分为两类:(原子类型) 、结构类型。 2.(数据结构)是计算机存储、组织数据的方式&#xff0c;是指相互之间存在一种或多种特定关系的数据元素的集合 3.代码重构指的是改变程序的(结构)而不改变其行为&#xff0c;以便提高代码的可读性、易修改性等。 4.软件实…

HCIA-Datacom题库(自己整理分类的)——OSPF协议多选

ospf的hello报文功能是 邻居发现 同步路由器的LSDB 更新LSA信息 维持邻居关系 下列关于OSPF区域描述正确的是 在配置OSPF区域正确必须给路由器的loopback接配置IP地址 所有的网络都应在区域0中宣告 骨干区域的编号不能为2 区域的编号范围是从0.0.0.0到255.255.255.255…

Python基础语法总结

1.每条语句结束不需要分号(也可以加上), 直接换行, 注意: 如果两行代码写一行, 则必须加分号. 2.定义变量不需要指定类型(如果需要写类型, 需要在变量名后面加": 类型, 这个写法只是方便读代码). 3.变量名大小写敏感. 4.查看变量类型: type(变量名). 5.Python中的int表…

Grafana Loki 组件介绍

Loki 日志系统由以下3个部分组成&#xff1a; Loki是主服务器&#xff0c;负责存储日志和处理查询。Promtail是专为loki定制的代理&#xff0c;负责收集日志并将其发送给 loki 。Grafana用于 UI展示。 Distributor Distributor 是客户端连接的组件&#xff0c;用于收集日志…

目标检测-One Stage-YOLOv1

文章目录 前言一、YOLOv1的网络结构和流程二、YOLOv1的损失函数三、YOLOv1的创新点总结 前言 前文目标检测-Two Stage-Mask RCNN提到了Two Stage算法的局限性&#xff1a; 速度上并不能满足实时的要求 因此出现了新的One Stage算法簇&#xff0c;YOLOv1是目标检测中One Stag…

小梅哥Xilinx FPGA学习笔记18——专用时钟电路 PLL与时钟向导 IP

目录 一&#xff1a;IP核简介&#xff08;具体可参考野火FPGA文档&#xff09; 二&#xff1a; 章节导读 三&#xff1a;PLL电路原理 3.1 PLL基本实现框图 3.2 PLL倍频实现 3.3 PLL分频实现 四: 基于 PLL 的多时钟 LED 驱动设计 4.1 配置 Clocking Wizard 核 4.2 led …

腾讯云服务器和轻量服务器选哪个好(各自的优势区别)

腾讯云轻量服务器和云服务器CVM该怎么选&#xff1f;不差钱选云服务器CVM&#xff0c;追求性价比选择轻量应用服务器&#xff0c;轻量真优惠呀&#xff0c;活动 https://curl.qcloud.com/oRMoSucP 轻量应用服务器2核2G3M价格62元一年、2核2G4M价格118元一年&#xff0c;540元三…