TSX详细说明(vue3方向)

介绍

Vue3 的 TSX 是 TypeScript + JSX 的组件写法,用 JavaScript 生成模板,兼具类型安全与编程灵活性,适合复杂动态场景,与 React JSX 相似但有 Vue 专属规则。

优点

  1. 强类型:TS 原生支持,减少运行时错误。
  2. 高灵活:直接写 JS 逻辑,复杂动态场景更高效。
  3. 全能力:可使用所有 JS 特性(循环、条件、函数)。
  4. 易集成:与 React 生态兼容,迁移成本低。

缺点

  1. 学习曲线:需熟悉 JSX 与 Vue 规则。
  2. 模板可读性:复杂结构不如 SFC 直观。
  3. 样式限制:无内置 scoped,需额外方案。
  4. 生态差异: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.css

    import 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)

原文出处:/show_1GW3L3QiBIfr.html