vue3+tsx开发语法详解
ztj100 2024-11-26 11:15 17 浏览 0 评论
很多组件库都使用了TSX的方式开发,主要因为其灵活性比较高,TSX和SFC开发的优缺点就不介绍了,这里主要说一下将SFC项目改造为TSX的过程。
安装JSX库
pnpm install @vitejs/plugin-vue-jsx -D
安装完之后在vite.config.ts进行插件使用,代码如下:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
plugins: [
vue(),
vueJsx() //插件使用
],
});
然后就可以愉快的使用TSX来开发Vue组件了,下面主要说一下SFC和TSX的部分区别。
基本语法对照 SFC
defineComponent 和 setup
SFC方式结构固定:template、script、style
<template>
<div>Hello World</div>
</template>
<script setup lang="ts">
</script>
<style scope>
</style>
TSX方式就完全是一个ts文件的写法,没有模板template和样式style
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
// Todo
return () => <div>Hello World</div>
}
})
setup中函数的返回值有多种方式,可以直接返回html:<div>Hello World</div>,这个适合结构简单的页面,如果返回比较多,可以使用如下方式:
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
// Todo
return () => (
<div>
<div>Child1</div>
<div>Child2</div>
<div>Child3</div>
</div>
)
}
})
如果是多节点,可以使用空符号包裹
return () => (
<>
<div>Child1</div>
<div>Child2</div>
<div>Child3</div>
</>
)
在以上的方式中我们把除了布局以外的逻辑都写在//Todo部分,但是有时候我们需要做一些按条件渲染的逻辑,那么也可以在return里加处理逻辑,例如:
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
// Todo
return () => {
if (something) {
return (
<div>
<div>Child1</div>
<div>Child2</div>
<div>Child3</div>
</div>
)
} else {
return (
<div>noChild</div>
)
}
}
}
})
这种方式类似v-if,但是和v-if还是有点区别,v-if可以作用在更小的范围,而这种方式只适合整个组件的条件渲染,这个可能不好理解,在下面v-if的使用中我们会看到区别。
v-if
使用条件判断语句来实现v-if的功能,一般是三目运算符
// SFC
<template>
<div>
<span v-if="condition ">A</span>
<span v-else>B</span>
</div>
</template>
// TSX
return () => (
<div>
{
condition ? <span>A</span> : <span>B</span>
}
</div>
)
在这里你可以看到v-if的使用和我们上面的条件返回不一样,区别就是整体渲染没有大的变化,只是其中部分地方要按条件显示。
v-bind
绑定变量,也就是简写的:冒号,修改方式就是将冒号去掉,把双引号改为大括号
// SFC
<template>
<div :class="c_class" :style="c_style" :custom="custom">
</template>
// TSX
return () => (
<div class={c_class} style={c_style} custom={custom}>
)
v-for
采用map循环的方式,返回一个数组
// SFC
<template>
<div v-for="(index, item) in list" :key="item">{{item}}</div>
</template>
// TSX
return () => (
<>
{
list.map((item, index) => <div>{item}</div>
}
</>
)
自定义指令
自定义指令和普通指令v-model一样
// SFC
<template>
<div v-custom="command">自定义指令</div>
</template>
// TSX
return () => (
<div v-custom={command}>自定义指令</div>
)
插槽
插槽有两种实现方式,一种是用v-slots绑定对象,一种是直接在元素中使用对象。
// SFC child
<template>
<div>
<slot>默认插槽: default</slot>
<br />
<slot name="header">具名插槽:header</slot>
<br />
<slot name="main" :var1="111" class="item" :var2="222">作用域插槽:main</slot>
</div>
</template>
// SFC parent
<template>
<div>
<child>
我就是默认的
<template #main="row"> 我是主要的{{ row.var1 }} </template>
<template #header>我是头</template>
</child>
</div>
</template>
// TSX child
import { defineComponent } from 'vue';
export default defineComponent({
setup(props, { slots }) {
return () => (
<div>
默认插槽: {slots.default && slots.default()}
<br />
具名插槽: {slots.header && slots.header()}
<br />
作用域插槽:{slots.main && slots.main({ name: '我是作用域插槽的传值' })}
</div>
);
}
});
// TSX parent 第一种方式
return () => (
<Child v-slots={{
default: () => '默认的内容是',
header: () => '我是有名称的',
main: (props: Record<'name', string>) => '我才是主要的' + props.name
}}>
</Child>
)
// 第二种方式
return () => (
<Child v-slots={{
default: () => '默认的内容是',
header: () => '我是有名称的',
main: (props: Record<'name', string>) => '我才是主要的' + props.name
}}>
</Child>
)
props
父组件向子组件传值
// SFC
defineProps<{
name: string,
childs: string[]
}>()
// TSX
export default defineComponent({
props: {
name: String,
childs: {
type: Array as PropsType<string[]>,
default: []
},
setup(props, { slots }) {
return () => <div>{props.name}</div>
}
})
需要注意的是,prop传递过来的值如果没有默认值,需要判断是否为空,可以使用计算属性或者条件渲染处理。
emit
子组件向父组件传值
// SFC
const emits = defineEmits<{
(e: 'changeName', name: string): void;
}>();
emits('changeName', '张三')
// TSX
export default defineComponent({
emits: ['changeName'],
setup(props, {emit}) {
emit('changeName', '张三')
return () => <div></div>
}
})
事件监听
事件监听就是v-on或者@,在TSX中事件以on开头,即使我们的自定义事件没有on,也要在监听的时候加上,一般都采用的是小驼峰的方式。
// SFC
<template>
<div @click="handleClick">无参数</div>
<div @click="(event) => handleClick1(event)">鼠标事件参数</div>
<div @click="handleClick2('abc')">自定义参数</div>
</template>
// TSX
return () => (
<>
<div onClick={handleClick}>无参数</div>
<div onClick={(event) => handleClick1(event)}>鼠标事件参数</div>
<div onClick={() => handleClick2('abc')}>自定义参数</div>
</>
);
// 函数定义相同
const handleClick = () => {
console.log('click');
};
const handleClick1 = (e: MouseEvent) => {
console.log(e.offsetX);
};
const handleClick2 = (name: string) => {
console.log(name);
};
自定义事件只需要在事件名前面加上on即可,参数传递与上面一致
// SFC
<div @custom="handleCustom()"></div>
// TSX
<div onCustom={handleCustom}></div>
在TSX中处理事件不能使用事件修饰符,因此需要在事件函数中自行处理,例如冒泡、阻止默认行为等。
属性/事件继承
对于这个我也不知道怎么描述,当我们给一个组件传递属性和事件时,一般子组件在props中接收属性值,emits中接收事件,但是我们也可以传一些额外的属性和事件,即不在props和emits中的属性和事件,虽然这是不推荐的做法,但是有时候当我们封装第三方库的时候,这种用法就非常的方便。具体看如下代码:
// parent name和click都不在子组件的明确接收中
<Child name="张三" @click="handleClick" />
// SFC child
<div v-bind="$attrs">继承属性/事件</div>
// TSX child
<div {...attrs}>继承事件</div>
SFC中,在template中我们可以通过$attrs获取到额外的属性和方法,script中可以通过getCurrentInstance方法获取组件对象,然后通过.attrs拿到属性和方法。
TSX中,直接通过attrs获取属性和方法,通过{...attrs}把属性和方法传递给子元素。
其他命令
v-show和v-model与SFC中使用一样,这里不做示例
组件引用
通过ref获取组件dom信息
// SFC
<ChildComponent ref="com" />
const com = ref(null)
// TSX
const com = ref(null)
return () => <ChildComponent ref={com} />
对外暴露属性和方法
在父组件中直接调用子组件的属性和方法
// SFC
defineExpose<{
name: Ref<string>;
handleClick: () => void;
}>({
name,
handleClick
});
// TSX
setup(props, { expose }) {
expose({name, handleClick})
}
样式修改
样式的改造一度是我切换TSX的最大痛点,因为在SFC中最麻烦的是修改第三方库的样式,一般要用到:deep,而且有时候还不一定成功,非常麻烦,改为TSX后我一直不知道怎么解决这种问题,后来搞定以后再回过头来看,发现是vue写久了养成了固定思维。我们在vue文件中写的样式都包含在scoped下面,如果不加scoped就可能会造成全局样式污染。那为什么会造成全局样式污染?又为什么加了scoped就不会呢?实际上我们只要知道CSS基础,明白CSS中的样式优先级即可。vue生成的项目最终还是会回归到html、css、js来,因此我们从这里来理解就方便多了。
- 为什么会造成全局样式污染?
这个不是vue的专利,而是css本身的优先级问题,就是如果我们定义了相同的css类,并以相同的方式来使用它,那么根据先后加载顺序,就会导致后加载的覆盖掉先加载的样式,造成先加载的样式无效,这就是所谓的样式污染。
- 为什么加了scoped就不会造成样式污染呢?
我们看一个简单的例子:
<div class="item">样式示例</div>
<style lang="less" scoped>
.item {
background-color: pink;
}
</style>
看一下html和css源码
可以看到,vue组件在渲染的时候,会给元素增加一个属性data-v-xxxx,然后在生成样式的时候也会在样式上加上[data-v-xxxxx],这是css属性选择器的用法,这样根据css选择器的优先级,这个属性就具有唯一性。
但是在TSX中没有了scoped怎么办?很简单,回归原始的css即可。在原始css中需要我们自己来保证css选择的唯一性,具体做法就是给组件内使用的css类都加上唯一前缀,例如组件名称为Child,那么所有的css类都加上child-xxx,因为我们肯定要保证组件名称的唯一性,所以这样下来对应的样式也就是唯一的。这就要求我们给所有需要修改样式的元素都加上类或者自定义属性,以便于我们可以通过唯一的css选择器选中它。
示例如下:
创建一个css文件:child.css
.child-item {
background-color: pink;
}
在tsx文件中引用
// TSX child.tsx
import './child.css'
return () => <div class="child-item"></div>
除了上面这种保证样式名称唯一的方式以外,vue其实一直为我们提供了另外一种方式-css module,具体来讲就是把css作为模块引入到js中,然后会生成一个唯一的名称,在以前用webpack的时候还需要装额外的包,现在vite已经帮我们集成了,只需要在vite.config.ts中加一下配置即可。
css: {
modules: {
localsConvention: 'camelCase'
}
}
这里规定css类名的命名规则为小驼峰,即child-item类在js中会变成childItem变量。但是要实现css module的功能,对css文件命名由要求,必须在后缀名前面是module,例如xxx.module.css、xxx.module.less、xxx.module.scss。
示例如下:
创建一个css文件:child.module.css
.child-item {
background-color: pink;
}
在tsx文件中引用
// TSX child.tsx
import styles from './child.module.css'
return () => <div class={styles.childItem}></div>
打开浏览器看一下源码
可以看到元素上绑定的css和全局的css都出现了变化,这种方式我们就不需要去关注编写的css是否是唯一的,vite会帮我们自行处理,只是在使用的时候有一些区别。
除了常规的css使用,我们还有动态class的使用。
// SFC
<div class="box" :class="{active: count === 1}"></div>
// TSX
<div class={['box', count===1 ? 'active' : '']}></div>
我们把需要的class处理成一个数组给它即可。
除了动态class还有动态style的使用。
// SFC
<div :style="{ height: height + 'px' }"></div>
// TSX
<div style={{ height: height.value + 'px' }}></div>
总结
目前对于大部分场景的使用都写到了,如果后期有其他的语法糖在进行补充,示例项目:vue3-tsx-todo,请在gitee上进行搜索
相关推荐
- 如何将数据仓库迁移到阿里云 AnalyticDB for PostgreSQL
-
阿里云AnalyticDBforPostgreSQL(以下简称ADBPG,即原HybridDBforPostgreSQL)为基于PostgreSQL内核的MPP架构的实时数据仓库服务,可以...
- Python数据分析:探索性分析
-
写在前面如果你忘记了前面的文章,可以看看加深印象:Python数据处理...
- C++基础语法梳理:算法丨十大排序算法(二)
-
本期是C++基础语法分享的第十六节,今天给大家来梳理一下十大排序算法后五个!归并排序...
- C 语言的标准库有哪些
-
C语言的标准库并不是一个单一的实体,而是由一系列头文件(headerfiles)组成的集合。每个头文件声明了一组相关的函数、宏、类型和常量。程序员通过在代码中使用#include<...
- [深度学习] ncnn安装和调用基础教程
-
1介绍ncnn是腾讯开发的一个为手机端极致优化的高性能神经网络前向计算框架,无第三方依赖,跨平台,但是通常都需要protobuf和opencv。ncnn目前已在腾讯多款应用中使用,如QQ,Qzon...
- 用rust实现经典的冒泡排序和快速排序
-
1.假设待排序数组如下letmutarr=[5,3,8,4,2,7,1];...
- ncnn+PPYOLOv2首次结合!全网最详细代码解读来了
-
编辑:好困LRS【新智元导读】今天给大家安利一个宝藏仓库miemiedetection,该仓库集合了PPYOLO、PPYOLOv2、PPYOLOE三个算法pytorch实现三合一,其中的PPYOL...
- C++特性使用建议
-
1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...
- Qt4/5升级到Qt6吐血经验总结V202308
-
00:直观总结增加了很多轮子,同时原有模块拆分的也更细致,估计为了方便拓展个管理。把一些过度封装的东西移除了(比如同样的功能有多个函数),保证了只有一个函数执行该功能。把一些Qt5中兼容Qt4的方法废...
- 到底什么是C++11新特性,请看下文
-
C++11是一个比较大的更新,引入了很多新特性,以下是对这些特性的详细解释,帮助您快速理解C++11的内容1.自动类型推导(auto和decltype)...
- 掌握C++11这些特性,代码简洁性、安全性和性能轻松跃升!
-
C++11(又称C++0x)是C++编程语言的一次重大更新,引入了许多新特性,显著提升了代码简洁性、安全性和性能。以下是主要特性的分类介绍及示例:一、核心语言特性1.自动类型推导(auto)编译器自...
- 经典算法——凸包算法
-
凸包算法(ConvexHull)一、概念与问题描述凸包是指在平面上给定一组点,找到包含这些点的最小面积或最小周长的凸多边形。这个多边形没有任何内凹部分,即从一个多边形内的任意一点画一条线到多边形边界...
- 一起学习c++11——c++11中的新增的容器
-
c++11新增的容器1:array当时的初衷是希望提供一个在栈上分配的,定长数组,而且可以使用stl中的模板算法。array的用法如下:#include<string>#includ...
- C++ 编程中的一些最佳实践
-
1.遵循代码简洁原则尽量避免冗余代码,通过模块化设计、清晰的命名和良好的结构,让代码更易于阅读和维护...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)