Vue3提供TSX支持

Vue中常用Template写模板,但也可以使用tsx。

Vue2就已经支持jsx写法,但友好度不高。因为Vue3对TypeScript的支持度高,所以tsx写法越来越被接受。

使用前需要安装@vitejs/plugin-vue-jsx插件。

安装完后在vite.config.ts引入:

1
2
3
4
5
6
7
8
9
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
plugins: [
vueJsx(),
// ...省略
],
// ...省略
})

使用TSX

创建一个App.tsx文件并使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { defineComponent } from "vue"


// 方式1:返回一个渲染函数
// export default function() {
// const code = 114514
// return (<div>{code}</div>)
// }

// 方式2:使用defineComponent + Options API
// export default defineComponent({
// data() {
// return {
// age:24,
// }
// },
// render() {
// return (<div>{this.age}岁,是学生</div>)
// }
// })

// 方式3:使用defineComponent + setup函数模式
export default defineComponent({
setup() {
const name = "野兽先辈"
return ()=> (<div>{name}</div>)
}
})

直接引入,可以类似组件一样使用

1
2
3
4
5
6
7
8
9
<template>
<div>
<ABC></ABC>
</div>
</template>

<script setup lang="ts">
import ABC from "./App"
</script>

TSX和Vue语法

ref响应式和v-show

1
2
3
4
5
6
7
8
9
10
export default defineComponent({
setup() {
const name = "野兽先辈"
const flag = ref(false)

// ref在template中会自动解包,但在tsx中不会,需要加上.value
// tsx中可以直接使用v-show
return ()=> (<div v-show={flag.value}>{name}</div>)
}
})

v-if

1
2
3
4
5
6
7
8
9
10
11
12
13
export default defineComponent({
setup() {
const name = "野兽先辈"
const flag = ref(false)

// tsx中不支持v-if,用了就报错
// return ()=> (<div v-if={flag.value}>{name}</div>)
// 真的要用的话就利用编程思想(三元表达式代替v-if)
return ()=> (<>
<div>{flag.value? <div>true</div>: ""}</div>
</>)
}
})

v-for和v-bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export default defineComponent({
setup() {
const data = [
{
name: "KNN"
},
{
name: "SNNN"
},
{
name: "YJSP"
},
{
name: "RU"
},
{
name: "BNKRG"
},
{
name: "SIK"
},
]
// tsx也不能使用v-for,也要用编程思想(Array.map代替v-for)
// 使用v-bind时可以直接写属性名并传值
return ()=> (<>
{data.map(v=> {
return<div name={v.name}>{v.name}</div>
})}
</>)
}
})

props和emits

App.tsx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
interface Props {
name?: string
}

export default defineComponent({
props: {
name:String
},
emits: ["on-click"],
setup(props:Props,{emit}) {
const data = [
{
name: "KNN"
},
{
name: "SNNN"
},
{
name: "YJSP"
},
{
name: "RU"
},
{
name: "BNKRG"
},
{
name: "SIK"
},
]
const click = (item:any)=> {
console.log("click",item)
emit("on-click",item)
}
return ()=> (<>
<div>props:{props?.name}</div>
<hr />
{data.map(v=> {
// 函数柯里化
return<div onClick={()=>click(v)}>{v.name}</div>
})}
</>)
}
})

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<ABC name="yajue" @on-click="click2"></ABC>
</div>
</template>

<script setup lang="ts">
import ABC from "./App"

const click2 = (item:any)=> {
console.log("click2",item)
}
</script>

插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 渲染函数,作为提供插槽的子组件
const A = (_:any,{slots}:any)=> (<>
<div>{slots.default? slots.default(): "默认值"}</div>
<div>{slots.foo?.()}</div>
</>)

export default defineComponent({
setup() {
// 使用对象定义插入插槽的内容
const slot = {
default: ()=> (<div>default slots</div>),
foo: ()=> (<div>foo slots</div>)
}
return ()=> (<>
<A v-slots={slot}></A>
</>)
}
})

v-model

1
2
3
4
5
6
7
8
9
10
export default defineComponent({
setup() {
const v = ref<string>("")
// v-model可以直接用
return ()=> (<>
<input v-model={v.value} type="text" />
<div>{v.value}</div>
</>)
}
})

Vite插件制作

用到的库:

1
2
3
4
5
npm install @vue/babel-plugin-jsx
npm install @babel/core
npm install @babel/plugin-transform-typescript
npm install @babel/plugin-syntax-import-meta
npm install @types/babel__core

Babel常用于语法转换,比如把ES6语法转换为ES5,提高低版本浏览器的兼容性。

编译原理

在项目根目录新建plugin/index.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// vite插件制作方式

import type { Plugin } from "vite"
// @babel/core 核心功能,将源代码转换成目标代码
import * as babel from "@babel/core"
// Vue给babel写的插件 支持tsx v-model等
import jsx from "@vue/babel-plugin-jsx"

export default function(): Plugin {
return {
name: "vite-plugin-vue-tsx",
// 把代码(code)和路径(id)返回成这个函数的参数
async transform(code, id) {
// 匹配文件类型
if(/.tsx$/.test(id)) {
// @ts-ignore
const ts = await import("@babel/plugin-transform-typescript").then(r=>r.default)
const res = await babel.transformAsync(code,{
// ast:抽象语法树,源代码语法结构的一种抽象表示
ast:true,
// 默认搜索默认babel.config.json文件
configFile:false,
// .babelrc.json
babelrc:false,
// 添加babel插件
plugins:[jsx,[ts,{isTSX:true,allowExtensions:true}]]
})
return res?.code
}
return code
}
}
}

同时在tsconfig的include中加入该路径,就可以作为插件使用了。