Vue 3 SFC 支持在 <script setup lang="tsx"> 中直接使用 JSX 渲染逻辑,无需改后缀为 .tsx;只需配置 @vitejs/plugin-vue 和 @vitejs/plugin-vue-jsx 双插件并返回 JSX 函数即可兼顾样式作用域与动态表达力。

Vue 3 单文件组件(SFC)中完全支持在 <script setup lang="tsx"> 或 <script lang="tsx"> 中使用 JSX/TSX 编写渲染逻辑,无需把 .vue 文件改成 .tsx。这种方式兼顾了 SFC 的结构优势(样式作用域、资源管理)和 JSX 的表达力,特别适合处理动态表单、配置化区块、递归菜单等复杂渲染场景。
关键不是换文件后缀,而是启用渲染函数 + 正确配置
你只需要两步:
- 在
script标签里声明lang="tsx" -
setup函数返回一个 JSX 渲染函数(即return () => <div>...</div>)
例如,一个根据字段类型动态渲染控件的表单项:
立即学习“前端免费学习笔记(深入)”;
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
<script setup lang="tsx">
import { ref, computed } from 'vue'
interface Field {
type: 'input' | 'select' | 'switch' | 'textarea'
label: string
value: any
props?: Record<string, any>
}
const props = defineProps<{ field: Field }>()
const emit = defineEmits(['update:value'])
const handleChange = (val: any) => {
emit('update:value', val)
}
// 渲染函数直接用 JSX 表达分支逻辑
return () => (
<div class="form-item">
<label>{props.field.label}</label>
{props.field.type === 'input' && (
<input
value={props.field.value}
placeholder={props.field.props?.placeholder}
onInput={(e) => handleChange((e.target as HTMLInputElement).value)}
/>
)}
{props.field.type === 'select' && (
<select
value={props.field.value}
disabled={props.field.props?.disabled}
onChange={(e) => handleChange(e.target.value)}
>
{(props.field.props?.options || []).map((opt: any) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
)}
{props.field.type === 'switch' && (
<label class="switch">
<input
type="checkbox"
checked={props.field.value}
onChange={(e) => handleChange(e.target.checked)}
/>
<span class="slider" />
</label>
)}
</div>
)
</script>
<style scoped>
.form-item { margin-bottom: 12px; }
</style>为什么推荐在 .vue 里用 TSX 而非另建 .tsx 文件?
- ✅ 样式作用域(
<style scoped>)依然生效 - ✅ 可直接 import 其他
.vue组件,无需手动 resolveComponent - ✅ 不用改路由或入口引用路径(
import Foo from './Foo.vue'照常) - ✅ 类型推导更稳:
defineProps和defineEmits在lang="tsx"下有完整 TS 支持
需要提前配好的环境依赖(Vite 项目)
确保 vite.config.ts 同时启用两个插件:
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [vue(), vueJsx()] // 缺一不可
})如果只加 vue(),.vue 中的 lang="tsx" 会报错:JSX 元素隐式具有类型 'any';如果只加 vueJsx(),.vue 模板本身又无法解析。
常见坑与绕过方式
- 插槽不能用
<template #default>,要通过ctx.slots.default?.()或props.children访问 -
v-model没有语法糖,需手动拆解为value+onUpdate:value - 事件名用驼峰:
onClick、onInput,不是@click或@input - 动态组件用
resolveComponent或直接导入变量:<MyComp />,不推荐<component :is="compName" />
这种写法不是替代 template,而是在“逻辑决定结构”的地方补上 template 做不到的灵活性。模板适合静态结构,JSX 适合数据驱动 UI。

















