介绍
Vue3 的 TSX 是 TypeScript + JSX 的组件写法,用 JavaScript 生成模板,兼具类型安全与编程灵活性,适合复杂动态场景,与 React JSX 相似但有 Vue 专属规则。
优点
- 强类型:TS 原生支持,减少运行时错误。
- 高灵活:直接写 JS 逻辑,复杂动态场景更高效。
- 全能力:可使用所有 JS 特性(循环、条件、函数)。
- 易集成:与 React 生态兼容,迁移成本低。
缺点
- 学习曲线:需熟悉 JSX 与 Vue 规则。
- 模板可读性:复杂结构不如 SFC 直观。
- 样式限制:无内置 scoped,需额外方案。
- 生态差异:Vue 生态以 SFC 为主,部分库适配不足。
适用场景
- ✅ 复杂动态表单/表格
- ✅ 高度定制化组件(如可视化编辑器)
- ✅ 泛型/递归组件
- ✅ 团队熟悉 React 技术栈
- ❌ 简单静态页面(优先 SFC)
概念
- TSX = TypeScript + JSX:在
.tsx文件或<script setup lang="tsx">中编写组件。 - 本质:编译为 Vue 渲染函数(
h函数),替代模板字符串。
与 .vue 对比
| 维度 | .vue(SFC) | .tsx |
|---|---|---|
| 模板 | 标签式,直观 | JS 表达式,灵活 |
| 类型 | 需额外配置 | 原生 TS 强类型 |
| 动态逻辑 | 依赖指令/计算属性 | 直接写 JS 逻辑 |
| 样式 | 内置 scoped | 需 CSS-in-JS/模块 |
| 适用场景 | 简单静态页面 | 复杂动态组件 |
Vue TSX 与 React JSX 关键差异
- 属性:Vue 用
class/for,React 用className/htmlFor。 - 指令:Vue 支持
v-if/v-for等,React 无指令靠条件渲染。 - 插槽:Vue 用
slots/defineSlots,React 用children。 - 响应式:Vue 自动追踪,React 依赖
useState/useEffect。
环境配置
Vite + Vue3 + TS
1. 安装依赖
npm install -D @vitejs/plugin-vue-jsx
# yarn add -D @vitejs/plugin-vue-jsx
# pnpm add -D @vitejs/plugin-vue-jsx
2. Vite 配置(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()] // 启用 JSX/TSX 支持
})
3. TS 配置(tsconfig.json)
{
"compilerOptions": {
"jsx": "preserve", // 保留 JSX 供 Vue 处理
"jsxImportSource": "vue", // 使用 Vue 的 JSX 类型
"jsxFactory": "h", // 渲染函数
"jsxFragmentFactory": "Fragment" // 片段
}
}
基础用法
1. 独立 .tsx 文件(推荐)
// Hello.tsx
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'Hello',
setup() {
const msg = ref('Hello TSX')
return () => (
<div class="hello">
<h1>{msg.value}</h1>
</div>
)
}
})
2. .vue 文件中使用 <script setup lang="tsx">
<!-- Hello.vue -->
<script setup lang="tsx">
import { ref } from 'vue'
const msg = ref('Hello TSX')
</script>
<template>
<!-- 混合模板与 TSX -->
<div>
<component is={() => <h1>{msg.value}</h1>} />
</div>
</template>
3. 渲染函数(h 函数,底层)
import { defineComponent, h } from 'vue'
export default defineComponent({
setup() {
return () => h('div', { class: 'hello' }, [h('h1', 'Hello TSX')])
}
})
语法详解
1. 模板基础
- 标签:同 HTML,支持原生与自定义组件。
- 表达式:用
{}包裹,如{msg.value}。 - 片段:用
<>或<Fragment>包裹多根节点。return () => ( <> <div>第一行</div> <div>第二行</div> </> )
2. 属性与绑定
(1)普通属性
// 静态
<div class="box" id="app"></div>
// 动态
<div class={isActive.value ? 'active' : ''}></div>
(2)v-bind(:)
// 单个
<div title={title.value}></div>
// 批量
<div {...{ title: title.value, id: 'app' }}></div>
(3)v-on(@)
<button onClick={handleClick}>点击</button>
// 带参数
<button onClick={() => handleSubmit('ok')}>提交</button>
3. 指令支持
Vue TSX 支持部分指令,需注意写法差异:
- ✅
v-if/v-else:直接用 - ✅
v-for:用map替代 - ✅
v-show:直接用 - ❌
v-model:需用v-model或modelValue - ❌
v-slot:用slots
// v-if
{isShow.value && <div>显示</div>}
// v-for
{list.value.map(item => <div key={item.id}>{item.name}</div>)}
// v-show
<div v-show={isShow.value}>显示</div>
4. Props 与类型
(1)defineProps(TS 类型)
import { defineComponent, defineProps } from 'vue'
export default defineComponent({
props: {
msg: String,
count: {
type: Number,
default: 0
}
},
setup(props) {
return () => <div>{props.msg} {props.count}</div>
}
})
(2)泛型 Props(Vue3.3+)
import { defineComponent } from 'vue'
export default defineComponent<{
list: string[]
}>((props) => {
return () => <div>{props.list.join(',')}</div>
})
5. 插槽(Slots)
(1)默认插槽
// 子组件 Child.tsx
import { defineComponent } from 'vue'
export default defineComponent({
setup(props, { slots }) {
return () => <div class="child">{slots.default?.()}</div>
}
})
// 父组件使用
<Child>
<p>插槽内容</p>
</Child>
(2)命名插槽
// 子组件
return () => (
<div>
{slots.header?.()}
{slots.default?.()}
{slots.footer?.()}
</div>
)
// 父组件
<Child>
{{
header: () => <h1>头部</h1>,
default: () => <p>内容</p>,
footer: () => <p>底部</p>
}}
</Child>
6. 响应式数据
- ref:需用
.value访问 - reactive:直接访问
import { ref, reactive } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const user = reactive({ name: 'Tom' })
return () => (
<div>
<p>{count.value}</p>
<p>{user.name}</p>
</div>
)
}
})
高级用法
1. 组件递归
// Tree.tsx
import { defineComponent } from 'vue'
interface TreeNode {
name: string
children?: TreeNode[]
}
export default defineComponent({
props: {
data: Object as () => TreeNode
},
setup(props) {
return () => (
<div class="tree-node">
<div>{props.data.name}</div>
{props.data.children?.map(child => (
<Tree key={child.name} data={child} />
))}
</div>
)
}
})
2. 动态组件
import { defineComponent, shallowRef } from 'vue'
import ComponentA from './A'
import ComponentB from './B'
export default defineComponent({
setup() {
const current = shallowRef(ComponentA)
return () => (
<div>
<component is={current.value} />
<button onClick={() => current.value = ComponentB}>切换</button>
</div>
)
}
})
3. 样式处理
内联样式:对象形式
<div style={{ color: 'red', fontSize: '16px' }}></div>CSS 模块:导入
.module.cssimport styles from './box.module.css' <div class={styles.box}></div>CSS-in-JS:使用
styled-components等
常见问题
1. TSX 中 ref 不生效
- 原因:TSX 不会自动解包,需用
.value - 解决:
const count = ref(0)→{count.value}
2. 如何使用 v-model
// 组件
defineProps<{ modelValue: string }>()
defineEmits<{ (e: 'update:modelValue', value: string): void }>()
// 使用
<input v-model={modelValue} />
// 或
<input value={modelValue} onInput={(e) => emit('update:modelValue', e.target.value)} />
3. 如何配置全局组件
// main.ts
import { createApp } from 'vue'
import App from './App'
import MyButton from './MyButton'
const app = createApp(App)
app.component('MyButton', MyButton)