【Vue3干货】template setup 和 tsx 的混合开发实践

前言

一般而言,我们在用Vue的时候,都是使用模板进行开发,但其实Vue 中也是支持使用jsx 或 tsx的。 最近我研究了一下如何在项目中混合使用二者,并且探索出了一些模式, 本文就是我在这种开发模式下的一些总结和思考,希望能帮助到大家。

由于tsx 就是ts版本的 jsx,二者基本上可以认作一个东西,所以下文使用tsx的地方,一般而言对于jsx也同样适用。

模板开发的痛点

通常我们都是通过模板来定义我们的ui,这是官方推荐的编写方式,模板开发有很多优点,比如:

  • 简单易上手,接近原生html的编写模式。
  • 内置了自定义指令、事件修饰符等方便的功能。
  • 模板在编译期间会做一些优化例如静态提升等,所以它的性能会更好。
  • 内置 css scoped 方案,简单有效,使用体验优于 css module 和 css in js 方案

模板开发提供了指令、事件修饰符等功能,方便我们复用一些逻辑。 特别是 css scoped 方案,个人觉得比 css module 和 css in js 方案更直观好用。

然而纯模板开发也有一些缺点,比如:

  • 不够灵活,模板语法由于不是标准的js语法,不够灵活,在实现一些复杂场景的时候,便会显得束手束脚,写出的代码会变得有些臃肿。
  • 难以复用模板片段,基于.vue 文件的单文件方案导致无法在同一个文件中定义多个组件,很多时候我们不得不新建一个个的小文件来创建组件,使得我们组件拆分不那么灵活;

tsx 开发的痛点

上面我们说到模板不够灵活,而由于这方面恰好是 tsx 的长处,借助一些工具如vite-plugin-jsx 的帮助,我们可以很方便地在 vue3 中使用 tsx 来开发,下面是一个在vue3中使用tsx开发的例子。

import { defineComponent, ref } from "vue";export default defineComponent({name: "TsxDemo",props: {msg: {type: String,required: true,default: "hello wrold",},},setup(props) {const count = ref(0);return () => (<div><header>{props.msg}</header><main>{count.value}</main></div>);},
});

vue 中使用 tsx 开发还有一些需要注意的点,但由于本文重点不在于教大家tsx基础,所以我们这里按下不表,这里贴一个链接方便大家学习(Vue3 + TSX 最佳实践?不存在的 - 掘金 (juejin.cn))

tsx的优点:

  • 自由,由于jsx语法本质上就是在写js,所以写jsx基本是随心所欲,想怎么写就怎么写。
  • 便于组件拆分,可以在同一个文件中组织多个组件。

tsx 的缺点

  • 不能使用自定义指令、事件修饰符等功能。
  • 由于tsx直接就是运行时了,无法在编译期做一些优化,导致性能比不过模板;
  • 没法使用 defineProps defineEmits 等只能用在setup语法糖中的预编译宏;
  • 没法使用 css scoped, 不管是 css module 还是 css in js 都不如 css scoped 那么简洁直观;

由于 tsx 开发模式不是vue官方推荐的开发模式,没法使用一些内置的功能,但我觉得最遗憾的是没法使用.vue单文件组件提供的 css scoped 这种css方案,个人觉得该方案相较于css modules 和 css in js方案更加简单易用。 另外一个比较大的问题还在于,没法使用defineProps defineEmits这些setup script 语法糖,导致在写 ts 类型时,只能使用基于运行时的推导方案,我们看下面个例子。

export default defineComponent({props: {count: {type: Number,required: true,},person: {type: Object as PropType<{ name: string }>,},color: {type: String as PropType<"success" | "error" | "primary">,required: true,},},setup() {return () => <div>demo</div>;},
});

在这里,我们写props的定义的时,很多情况下需要依赖 as PropType<xxx> 来帮我们推断出更精确的类型,而在setup script中我们可以使用基于ts的类型方案,这种方式显然会更加地友好。

<script setup lang="tsx">
// 基于ts的类型推断
const props = defineProps<{count: number;person?: {name: string;};color?: "success" | "error" | "primary";
}>();
</script>

既然模板开发和tsx开发都有各有各的优缺点,那么有没有什么办法可以将他们的优点组合一下呢,答案即是我们今天要讨论的setup script + tsx 混合编程模式。

混合编程

那么如何开启 setup script + tsx 混合编程模式呢?

很简单将lang改为tsx 就可以

<script lang="tsx" setup></script>

首先我们按最常规的方法,定义一个子组件,并且渲染到父组件中:

<template><div><Demo msg="hello world" /></div>
</template><script lang="tsx" setup>
import { defineComponent } from "vue";const Demo = defineComponent({props: {msg: String,},setup(props) {return () => (<div><div>msg is {props.msg}</div></div>);},
});
</script>

这就是最初始的状态,我们将在.tsx 中写组件的方式搬到了 setup script 中, Demo组件接受一个类型为string的属性msg,这段代码在浏览器中最终会渲染成

现在我们它加上样式,由于我们是在.vue文件中编写 Demo 组件,所以我们可以运用单文件内置的css方案。 .vue 文件中支持 css module 和 css scoped 方案,这两种方式都可以用,我们先看 css module方案

<template><div><Demo msg="hello world" /></div>
</template><script lang="tsx" setup>
import { defineComponent, useCssModule } from "vue";const styles = useCssModule();const Demo = defineComponent({props: {msg: String,},setup(props) {return () => (<div class={styles.wrapper}><div class={styles.inner}>msg is {props.msg}</div></div>);},
});
</script><style lang="less" module>
.wrapper {.inner {color: red;}
}
</style>

可以看到,完美生效,我们再来看一下 css scoped方案:

<template><div><Demo msg="hello world" /></div>
</template><script lang="tsx" setup>
import { defineComponent } from "vue";const Demo = defineComponent({props: {msg: String,},setup(props) {return () => (<div class="wrapper"><div class="inner">msg is {props.msg}</div></div>);},
});
</script><style lang="less" scoped>
.wrapper {.inner {color: red;}
}
</style>

可以看到,并没有生效,这是因为Demo是一个子组件,而scoped方案不会透传到子组件中dom中,所以这里我们得用:deep处理下:

<style lang="less" scoped>
:deep(.wrapper) {.inner {color: red;}
}
</style>

再刷新下浏览器就可以看到css 生效了。

到这一步,通过用:deep做一下特殊处理,我们可以实现在 vue 中使用css scoped方案了。 那,能不能连 :deep都不写呢? 我们继续研究下。

这里之所以需要:deep 特殊处理的原因在于Demo 是一个组件,而css scoped默认不会透传到子组件中,那么如何去规避这个问题呢?

其实,Demo组件本质上是要实现一个生成一棵vnode 树,而这可以通过函数去生成:

const renderDemo = (props:{msg:string}) => {<div class="wrapper"><div class="inner">msg is {props.msg}</div></div>
}

现在我们需要将这个函数生成的 vnode 插入到模板中。

在tsx 中,我们经常可以看到这样的写法

  setup() {const header = <header>this is header</header>;const renderMain = () => <main>this is main</main>;const renderFooter = () => <footer>this is footer</footer>;return () => (<div>{header}{renderMain()}{Math.random() > 0.5 ? renderFooter() : null}</div>);},

我们将组件的各个部分通过拆分成了一个个子单元,它可以是一个单独的vnode,也可以是一个渲染函数,然后通过 {} 表达式嵌入主单元。那么,在混编模式下我们能不能这么做呢?

模板中有{{}}表达式,也是接受动态的内容,顺着这个思路,我们可以写出这样的代码:

<template><div>{{ header }}{{ renderMain() }}{{ Math.random() > 0.5 ? renderFooter() : null }}</div>
</template><script lang="tsx" setup>
const header = <header>this is header</header>;
const renderMain = () => <main>this is main</main>;
const renderFooter = () => <footer>this is footer</footer>;
</script>

然而,渲染出来的结果却是会报错.

原因是{{}} 与 {} 不同,其实是用来渲染动态字符串的,这里传vnode肯定是不行的,那么这里该怎么做呢?

答案是<component :is='xxx'>

<component :is='xxx'> 这里传的is既支持传组件,也支持传vnode,所以我们可以这样写。

<template><div><component :is="header"></component><component :is="renderMain()"></component><component :is="Math.random() > 0.5 ? renderFooter() : null"></component></div>
</template><script lang="tsx" setup>
const header = <header>this is header</header>;
const renderMain = () => <main>this is main</main>;
const renderFooter = () => <footer>this is footer</footer>;
</script>

渲染也是正常的

所以,之前的Demo也可以这样写

<template><div><component :is="renderDemo('hello world')"></component></div>
</template><script lang="tsx" setup>
const renderDemo = (msg: string) => (<div><div> msg is {msg}</div></div>
);
</script>

之前我们的写法是通过一个将这个片段拆分为一个组件,导致我们在用css scoped 方案的时候,必须要用 :deep() 去透传一下,而这种写法支持渲染一个vnode,所以没有这个限制,也就是说我们写css 可以按照正常的写法去写,即:

<template><div><component :is="renderDemo('hello world')"></component></div>
</template><script lang="tsx" setup>
const renderDemo = (msg: string) => (<div class="wrapper"><div class="inner"> msg is {msg}</div></div>
);
</script><style lang="less" scoped>
.wrapper {.inner {color: red;}
}
</style>

而渲染出的组件也是正常的

到此为止我们已经能将 tsx 和 css scoped 完美结合起来了,那还能更优化吗?

我们来看下面这个计数器 demo:

// Counter.vue<template><component :is="render()"></component>
</template><script setup lang="tsx">
const props = defineProps<{count: number;
}>();const emits = defineEmits(["update:count"]);const render = () => {return (<div><div class="content">count is {props.count}</div><div><buttononClick={() => {emits("update:count", props.count + 1);}}>add</button></div></div>);
};
</script><style lang="less" scoped>
.content {color: red;
}
</style>// Parent.vue<template><Counter v-model:count="count" />
</template><script setup lang="ts">
import { ref } from "vue";
import Counter from "./views/Counter.vue";const count = ref(0);
</script>

我们直接将所有的渲染逻辑抽象为一个 render 函数,然后在模板部分,只用

<template> <component :is="render()"></component> 
</template>

这样写, 这就相当于我们完全实现了在template setup 模式中,完全使用tsx 去开发, 使得tsx 可以使用 defineProps defineEmits 等宏语法的支持;从渲染结果可以看出,点击逻辑可以生效,在我们设置的样式也不需要 :deep 加持就能作用到dom中;

这就是终极黑魔法!

总结

上面啰啰嗦嗦说了一大堆,其实总结起来就是

  • <script setup lang="tsx"> 开启tsx 混编;
  • 直接在 创建vnode 或者render 函数;
  • 借用<component :is="render()" />插入到模板中;

这是我们常用的混合使用setup template 和tsx 的方式,既能实现 vnode 的复用,又可以完美地和 css scoped结合,推荐大家在一些需要灵活的场景下使用这种模。


原文链接:
https://juejin.cn/post/7282692088016437307

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

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

相关文章

华为配置策略路由(基于IP地址)示例

组网需求 如图1所示&#xff0c;汇聚层Switch做三层转发设备&#xff0c;接入层设备LSW做用户网关&#xff0c;接入层LSW和汇聚层Switch之间路由可达。汇聚层Switch通过两条链路连接到两个核心路由器上&#xff0c;一条是高速链路&#xff0c;网关为10.1.20.1/24&#xff1b;另…

基于大语言模型LangChain框架:知识库问答系统实践

ChatGPT 所取得的巨大成功&#xff0c;使得越来越多的开发者希望利用 OpenAI 提供的 API 或私有化模型开发基于大语言模型的应用程序。然而&#xff0c;即使大语言模型的调用相对简单&#xff0c;仍需要完成大量的定制开发工作&#xff0c;包括 API 集成、交互逻辑、数据存储等…

Databend 开源周报第 125 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 密码策略 Data…

智能优化算法应用:基于浣熊算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于浣熊算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于浣熊算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.浣熊算法4.实验参数设定5.算法结果6.参考文献7.MA…

麒麟V10arm桌面版的安装包在麒麟V10服务器版安装

安装过后&#xff0c;可执行程序可能运行不了&#xff0c;看起来就像没识别为可执行程序。在终端运行&#xff0c;会发现其实是缺少了某些库&#xff0c;比如libicui18n.so.66、libicuuc.so.66、libicudata.so.66和libm.so.6库版本不对。 报这个错&#xff1a;error while loa…

Unity VR Pico apk安装失败:INSTALL_FAILED_UPDATE_INCOMPATIBLE

我的报错&#xff1a; PICO4企业版。安装apk&#xff0c;报错“安装失败。&#xff08;所属的Unity项目打包的apk&#xff0c;被我在同一台pico4安装了20次&#xff09; 调试方法&#xff1a; PIco4发布使用UNITY开发的Vr应用&#xff0c;格式为apk&#xff0c;安装的时候发生…

Quartz持久化(springboot整合mybatis版本实现调度任务持久化)--提供源码下载

1、Quartz持久化功能概述 1、实现使用quartz提供的默认11张持久化表存储quartz相关信息。 2、实现定时任务的编辑、启动、关闭、删除。 3、实现自定义持久化表存储quartz定时任务信息。 4、本案例使用springboot整合mybatis框架和MySQL数据库实现持久化 5、提供源码下载 …

众和策略:12月新批国产网游版号数量过百

上星期五&#xff08;22日&#xff09;&#xff0c;A股冲高回落&#xff0c;三大股指挨近午盘拉升走高&#xff0c;午后再度回落走低&#xff0c;沪指尾盘跌幅收窄。到收盘&#xff0c;沪指跌0.13%报2914.78点&#xff0c;深成指跌0.39%报9221.31点&#xff0c;创业板指跌0.37%…

构建外卖系统:从技术到实战

在当今高度数字化的社会中&#xff0c;外卖系统的开发变得愈发重要。本文将从技术角度出发&#xff0c;带领读者一步步构建一个基础的外卖系统&#xff0c;并涵盖关键技术和实际代码。 1. 技术选型 1.1 后端开发 选择Node.js和Express框架进行后端开发&#xff0c;搭建一个灵…

Kruskal算法求最小生成树(kruskal算法)

题目描述 给定一个 n 个点 m 条边的无向图&#xff0c;图中可能存在重边和自环&#xff0c;边权可能为负数。 求最小生成树的树边权重之和&#xff0c;如果最小生成树不存在则输出 impossible。 给定一张边带权的无向图 G(V,E)&#xff0c;其中 V 表示图中点的集合&#xff…

【操作系统】探究进程奥秘:显示进程列表的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;Linux专栏&#xff1a;《探秘Linux | 操作系统解密》⏰诗赋清音&#xff1a;月悬苍穹泛清辉&#xff0c;梦随星河徜徉辉。情牵天际云千层&#xff0c;志立乘风意自飞。 ​ 目录 &a…

OpenCV之图像匹配与定位

利用图像特征的keypoints和descriptor来实现图像的匹配与定位。图像匹配算法主要有暴力匹配和FLANN匹配&#xff0c;而图像定位是通过图像匹配结果来反向查询它们在目标图片中的具体坐标位置。 以QQ登录界面为例&#xff0c;将整个QQ登录界面保存为QQ.png文件&#xff0c;QQ登…

IDEA2023版如何创建web项目

一、新建项目 点击File->New->Project...&#xff0c;如果是第一次创建项目则单击New Project 二、添加Web Application 建好的样子 把web移动到main目录下同时改名为webapp 三、不存在Add Framework Support添加Web Application 如何存在Add Framework Support&#x…

运维工程师的出路揭秘:跨越35岁半衰期,探寻职业发展新路径

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 目录 写在前面 本章主题 一.35岁被称为运维半衰期…

800+顶尖架构师齐聚深圳,第十届GIAC全球互联网架构大会,分享行业前沿视角与技术架构落地实践思考!(附:大会核心PPT下载)

2023年6月30-7月1日&#xff0c;由MSUP与高可用架构社区、深圳市软件行业协会联合主办的GIAC全球互联网架构大会在深圳华侨城洲际酒店圆满落幕。 本届大会邀请到了阿里、美图、腾讯、字节跳动、顺丰、华为、快手、B站等多个行业的近百位一线架构师、技术专家&#xff0c;围绕AI…

类加载器及其类加载子系统

类加载器子系统作用 类加载器子系统的作用是负责将字节码文件加载到内存中&#xff0c;并将其转化为能够被虚拟机直接使用的形式。它是Java虚拟机的一部分&#xff0c;具体作用如下&#xff1a; 加载 类加载器负责将类的字节码文件加载到虚拟机的方法区中&#xff0c;以便…

L1-061:新胖子公式

题目描述 根据钱江晚报官方微博的报导&#xff0c;最新的肥胖计算方法为&#xff1a;体重(kg) / 身高(m) 的平方。如果超过 25&#xff0c;你就是胖子。于是本题就请你编写程序自动判断一个人到底算不算胖子。 输入格式&#xff1a; 输入在一行中给出两个正数&#xff0c;依次为…

白龙地铁消费项目(地铁消费系统,包括用户端、管理端)

大一学的C#可视化项目文件&#xff0c;所有功能均可使用。可以直接下载 下方是演示照片

sigmoid softmax优化

1.前言 最近在搞模型部署发现&#xff0c;推理速度不能满足我们需求&#xff0c;于是最近学习了优化算子技巧&#xff0c;学到了sigmoid&#xff0c;softmax算子优化&#xff0c;真的数学之美。2.sigmoid算子优化 一.算子优化图 我们根据sigmoid公式&#xff0c;我们进行求反…

谷歌公布 2023 年最受欢迎的 Chrome 扩展

2023年&#xff0c;谷歌公布了最受欢迎的Chrome扩展&#xff0c;共有12款涵盖了多个领域&#xff0c;从提升工作效率到游戏娱乐。这些扩展旨在增强用户的浏览体验和生产力。 Scribe 功能&#xff1a;使用AI记录工作流程并创建逐步指南。 特点&#xff1a;自动记录和生成详细…