Vue3中JSX/TSX的实战指南:从环境搭建到高级应用
2026/9/11 9:36:55 网站建设 项目流程

1. 项目概述:为什么要在Vue3中重新审视JSX?

最近在几个Vue3项目中,我尝试性地在一些特定场景下用JSX替换了传统的模板语法,结果发现开发体验和灵活性提升了不少。这让我意识到,很多Vue开发者,尤其是从React转过来或者对JSX有天然好感的同学,可能低估了JSX在Vue3生态中的成熟度和实用性。大家一提到Vue,第一反应就是.vue单文件组件和模板语法,这确实是Vue的特色和优势。但Vue3从设计之初就加强了对JSX(在Vue语境下常称为TSX,当与TypeScript结合时)的一等公民支持,这绝不是为了兼容而兼容,而是为了解决模板在某些复杂场景下的局限性。

简单来说,JSX在Vue3中是一个完全受支持的、强大的备选方案。它特别适合那些逻辑高度复杂、需要大量动态渲染逻辑、或者组件结构需要根据运行时数据灵活变化的场景。想象一下,你需要渲染一个根据后端返回的复杂JSON动态生成的表单,每个字段的类型、校验规则、联动关系都不同;或者你要实现一个类似可视化拖拽编辑器的渲染引擎,节点的类型和结构完全由数据驱动。在这些场景下,用模板写v-ifv-for的嵌套可能会让你陷入“模板地狱”,而JSX的JavaScript表达能力就能大显身手。它允许你将渲染逻辑像写普通JavaScript函数一样组织,享受完整的语言特性(如解构、三元表达式、数组方法等)带来的便利。接下来,我会结合具体实践,拆解如何在Vue3项目中巧妙、高效地运用JSX,让它成为你工具箱里一把趁手的“手术刀”,而不是一把笨重的“锤子”。

2. 环境搭建与基础配置:让JSX在Vue3中跑起来

要在Vue3中使用JSX,第一步就是正确配置开发环境。Vue3推荐使用Vite作为构建工具,它的开箱体验和热更新速度都非常优秀。

2.1 创建项目与安装核心插件

首先,使用Vite创建一个Vue3项目。如果你已经有一个现有的Vue3项目,可以跳过创建步骤,直接安装插件。

# 使用 npm 创建 npm create vite@latest my-vue-jsx-app -- --template vue-ts # 进入项目目录 cd my-vue-jsx-app # 安装 Vue3 的 JSX 官方支持插件 npm install @vitejs/plugin-vue-jsx -D

这里的关键是@vitejs/plugin-vue-jsx插件。它是Vite官方维护的插件,专门用于在Vue单文件组件或独立的.jsx/.tsx文件中编译JSX语法,将其转换为Vue3能识别的h()函数调用。注意要安装为开发依赖(-D)。

2.2 配置 Vite.config.ts

安装完插件后,需要在vite.config.ts中引入并配置它。

// vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' // 引入插件 // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), vueJsx(), // 添加到插件数组 ], })

这个配置同时支持了.vue文件(通过vue()插件)和.jsx/.tsx文件(通过vueJsx()插件)。现在,你就可以在项目中创建.tsx文件了。

2.3 创建你的第一个TSX组件

src/components目录下,创建一个HelloJsx.tsx文件。

// HelloJsx.tsx import { defineComponent, ref } from 'vue'; export default defineComponent({ name: 'HelloJsx', setup() { const count = ref(0); const increment = () => { count.value++; }; // 在setup中返回一个渲染函数 return () => ( <div class="hello-jsx"> <h1>Hello, JSX in Vue3!</h1> <p>Current count is: {count.value}</p> <button onClick={increment}>Click me</button> </div> ); }, });

这里有几个关键点:

  1. 使用defineComponent:这是定义Vue组件的标准方式,提供了更好的TypeScript类型推断。
  2. setup函数中编写逻辑setup是Vue3的组合式API入口。所有的响应式数据(ref,reactive)、计算属性(computed)、生命周期钩子(onMounted等)都在这里定义。
  3. 返回一个渲染函数setup函数最后返回一个函数,这个函数返回的就是JSX描述的虚拟DOM。这是JSX组件最核心的写法。
  4. JSX语法:使用大括号{}来嵌入JavaScript表达式,如{count.value}。事件监听使用onClick(小驼峰),而不是模板中的@click

2.4 在Vue组件中使用TSX组件

App.vue中,你可以像使用普通Vue组件一样引入和使用它。

<!-- App.vue --> <script setup lang="ts"> import HelloJsx from './components/HelloJsx'; </script> <template> <div> <HelloJsx /> </div> </template>

至此,一个最基本的Vue3 + JSX开发环境就搭建完成了。你可以看到,JSX组件和SFC组件可以无缝混合使用。

注意:如果你在.vue文件的<script setup>中直接写JSX,目前需要一些额外的配置或实验性标志。更常见的做法是将复杂的JSX渲染部分抽离到独立的.tsx文件中,或者在setup()函数内返回渲染函数。保持关注Vue和Vite官方文档,这方面的支持正在不断完善。

3. JSX核心语法与Vue指令的映射

习惯了Vue模板的开发者,切换到JSX最大的障碍可能就是指令的写法不同。其实它们之间有着清晰的对应关系,理解后就能顺畅转换。

3.1 数据绑定与表达式

在模板中,我们使用双大括号{{ }}进行文本插值。在JSX中,任何JavaScript表达式都可以放在单大括号{}中。

  • 模板语法<span>{{ message }}</span>
  • JSX语法<span>{message}</span>

对于元素属性(Attribute)的绑定,模板使用:前缀或v-bind,而JSX同样使用大括号。

  • 模板语法<div :id="dynamicId" :class="{'active': isActive}"></div>
  • JSX语法<div id={dynamicId} class={{'active': isActive}}></div>
    • 注意:class绑定在JSX中通常直接写class,而不是className(这是与React的区别之一,@vitejs/plugin-vue-jsx默认处理了)。对于动态对象,直接传入对象即可。

3.2 条件渲染:v-if 与 v-show

模板中的v-if/v-else-if/v-elsev-show,在JSX中需要转换为JavaScript的条件语句。

  • v-if
    <!-- 模板 --> <div v-if="type === 'A'">Type A</div> <div v-else-if="type === 'B'">Type B</div> <div v-else>Type Other</div>
    // JSX - 使用三元表达式或逻辑与 return () => ( <div> {type === 'A' ? <div>Type A</div> : type === 'B' ? <div>Type B</div> : <div>Type Other</div>} </div> ); // 或者更清晰的方式:使用 if-else 语句块 const renderContent = () => { if (type === 'A') return <div>Type A</div>; if (type === 'B') return <div>Type B</div>; return <div>Type Other</div>; }; return () => <div>{renderContent()}</div>;
  • v-showv-show本质是控制CSS的display属性。在JSX中,你可以直接操作样式。
    return () => <div style={{ display: isVisible ? 'block' : 'none' }}>内容</div>;

3.3 列表渲染:v-for

v-for指令在JSX中被数组的map方法所取代。

  • 模板语法<li v-for="(item, index) in items" :key="item.id">{{ item.name }}</li>
  • JSX语法
    return () => ( <ul> {items.map((item, index) => ( <li key={item.id}>{item.name}</li> ))} </ul> );
    关键点key属性必须显式提供,和React中的要求一致。这对于Vue的虚拟DOM diff算法高效更新至关重要。

3.4 事件处理:v-on

模板中的@clickv-on:click,在JSX中变为onClick(小驼峰命名)。

  • 模板语法<button @click="handleClick">Click</button>
  • JSX语法<button onClick={handleClick}>Click</button>

事件修饰符(如.stop,.prevent)在JSX中没有直接的语法糖。你需要手动调用事件对象的方法。

  • 模板语法<form @submit.prevent="onSubmit">...</form>
  • JSX语法
    const onSubmit = (event: Event) => { event.preventDefault(); // 手动阻止默认行为 // ... 提交逻辑 }; return () => <form onSubmit={onSubmit}>...</form>;
    对于.stopevent.stopPropagation())等其他修饰符,处理方式类似。

3.5 双向绑定:v-model

v-model是Vue的一个语法糖,在JSX中需要拆解为value绑定和事件监听。Vue3的v-model支持多个绑定和自定义修饰符,在JSX中对应为modelValueonUpdate:modelValue

  • 单个v-model(默认)

    <!-- 模板 --> <input v-model="text" />
    // JSX import { defineComponent, ref } from 'vue'; export default defineComponent({ setup() { const text = ref(''); return () => ( <input value={text.value} onInput={(e) => { text.value = (e.target as HTMLInputElement).value; }} /> ); }, });

    更简洁的写法是使用Vue提供的vModel指令(需要从@vue/runtime-dom导入,但通常插件已处理),但直接拆解写更能理解其本质。

  • 多个v-model(如v-model:title

    <!-- 模板 --> <MyComponent v-model:title="pageTitle" v-model:content="pageContent" />
    // 在父组件中使用JSX return () => ( <MyComponent title={pageTitle.value} onUpdate:title={(val) => { pageTitle.value = val; }} content={pageContent.value} onUpdate:content={(val) => { pageContent.value = val; }} /> );

3.6 插槽:slot

插槽在JSX中的写法与React的childrenprops类似,但更贴近Vue的思维。

  • 默认插槽:在JSX中,子元素直接作为组件的子节点传递,它们就是默认插槽的内容。
    // 父组件 return () => ( <ChildComponent> <div>这是默认插槽的内容</div> </ChildComponent> ); // 子组件 ChildComponent.tsx import { defineComponent } from 'vue'; export default defineComponent({ setup(props, { slots }) { // 通过 setup 上下文解构出 slots return () => ( <div class="child"> <header>Child Header</header> {/* 渲染默认插槽 */} <main>{slots.default?.()}</main> <footer>Child Footer</footer> </div> ); }, });
  • 具名插槽:通过v-slots(在JSX中是一个对象属性)来传递。
    // 父组件 return () => ( <ChildComponent v-slots={{ header: () => <h1>自定义标题</h1>, footer: (slotProps: { text: string }) => <p>页脚: {slotProps.text}</p>, }} > <div>默认内容</div> </ChildComponent> ); // 子组件 ChildComponent.tsx export default defineComponent({ setup(props, { slots }) { return () => ( <div> {/* 渲染具名插槽 header */} <div>{slots.header?.()}</div> <div>{slots.default?.()}</div> {/* 向具名插槽 footer 传递参数 */} <div>{slots.footer?.({ text: '来自子组件的数据' })}</div> </div> ); }, });
    v-slots是Vue JSX插件提供的一个特殊属性,用于定义插槽内容。在子组件内部,通过setup的第二个参数解构出slots对象来访问和渲染它们。

3.7 属性透传与继承

在Vue中,未被组件propsemits声明的属性会作为$attrs自动透传到组件的根元素上。在JSX函数式组件或使用setup的组件中,你需要手动处理。

import { defineComponent, useAttrs } from 'vue'; export default defineComponent({ setup(props, { attrs }) { // 从上下文解构 attrs // 或者使用组合式API:const attrs = useAttrs(); return () => ( // 使用展开运算符将 attrs 应用到元素上 <div {...attrs}> 组件内容 </div> ); }, });

使用{...attrs}可以方便地将所有未声明的属性(如classstyle>// DynamicFormRenderer.tsx import { defineComponent, computed } from 'vue'; import TextInput from './TextInput'; import SelectInput from './SelectInput'; import CheckboxGroup from './CheckboxGroup'; // 组件映射表 const componentMap = { text: TextInput, select: SelectInput, checkbox: CheckboxGroup, } as const; type ComponentType = keyof typeof componentMap; interface FieldConfig { type: ComponentType; label: string; name: string; options?: Array<{label: string; value: any}>; // ... 其他配置 } export default defineComponent({ props: { fields: { type: Array as PropType<FieldConfig[]>, required: true, }, formData: { type: Object as PropType<Record<string, any>>, required: true, }, }, setup(props) { // 动态渲染每个字段 const renderField = (field: FieldConfig) => { const Component = componentMap[field.type]; // 根据类型获取组件 if (!Component) { return <div>Unsupported component type: {field.type}</div>; } // 准备传递给动态组件的props const componentProps = { modelValue: props.formData[field.name], 'onUpdate:modelValue': (val: any) => { // 更新表单数据,这里需要触发一个事件或使用Vuex/Pinia // 例如:context.emit('update:formData', { ...props.formData, [field.name]: val }); }, label: field.label, options: field.options, // ... 其他props }; return <Component key={field.name} {...componentProps} />; }; return () => ( <form> {props.fields.map(renderField)} </form> ); }, });

在这个例子中,componentMap建立了类型到组件的映射。renderField函数利用JavaScript的Map查找和JSX的动态组件渲染能力,清晰地表达了“根据类型选择组件并渲染”的逻辑。如果用模板写,可能需要一堆<component :is="...">配合复杂的v-if链,可读性和可维护性都不如JSX。

4.2 高阶组件与渲染劫持

高阶组件(HOC)是接收一个组件并返回一个新组件的函数。在JSX中,实现HOC非常自然,因为它本身就是JavaScript函数。

场景:创建一个withLoadingHOC,在数据加载时显示加载指示器。

// withLoading.tsx import { defineComponent, DefineComponent } from 'vue'; export function withLoading<P extends Record<string, any>>( WrappedComponent: DefineComponent<P> ) { return defineComponent({ props: { loading: { type: Boolean, default: false, }, // 透传原组件的所有props ...(WrappedComponent.props || {}), }, setup(props, { attrs, slots }) { return () => { if (props.loading) { return <div class="loading-spinner">加载中...</div>; } // 渲染被包裹的组件,并传递所有属性和插槽 return <WrappedComponent {...props} {...attrs} v-slots={slots} />; }; }, }); } // 使用示例 import UserProfile from './UserProfile.vue'; const UserProfileWithLoading = withLoading(UserProfile); // 在另一个组件中使用 export default defineComponent({ setup() { const user = ref(null); const loading = ref(true); onMounted(async () => { loading.value = true; user.value = await fetchUser(); loading.value = false; }); return () => ( <UserProfileWithLoading loading={loading.value} user={user.value} /> ); }, });

withLoading函数接收一个组件,返回一个新的组件。新组件增加了loadingprop,并根据其值决定渲染加载态还是原组件。这种逻辑复用模式在JSX中写起来行云流水。注意这里使用了v-slots={slots}来透传所有插槽内容。

4.3 复杂逻辑内联与代码复用

当渲染逻辑包含大量条件判断、循环或计算时,JSX允许你将逻辑直接内联在渲染函数中,或者轻松地提取为纯JavaScript函数,这比在模板中使用复杂的计算属性或方法更清晰。

场景:渲染一个树形结构的数据。

// TreeNode.tsx import { defineComponent, PropType } from 'vue'; interface TreeNode { id: string; label: string; children?: TreeNode[]; } export default defineComponent({ props: { node: { type: Object as PropType<TreeNode>, required: true, }, level: { type: Number, default: 0, }, }, setup(props) { // 一个纯函数,用于判断节点是否有特殊样式 const getNodeClass = (node: TreeNode) => { const classes = ['tree-node']; if (!node.children || node.children.length === 0) { classes.push('is-leaf'); } if (node.label.includes('重要')) { classes.push('is-important'); } return classes.join(' '); }; // 渲染函数本身 return () => { const { node, level } = props; const paddingLeft = `${level * 20}px`; return ( <div class={getNodeClass(node)} style={{ paddingLeft }}> <span>{node.label}</span> {node.children && node.children.length > 0 && ( <div class="children"> {node.children.map(child => ( <TreeNode key={child.id} node={child} level={level + 1} /> ))} </div> )} </div> ); }; }, });

这里,getNodeClass是一个纯函数,负责计算CSS类名。渲染函数内直接使用了三元表达式和map来递归渲染子节点。整个组件的逻辑是自包含且声明式的。如果使用模板,可能需要将getNodeClass定义为计算属性,递归部分可能还需要引入额外的组件或复杂的v-for,结构上不如JSX紧凑和直观。

4.4 与 TypeScript 的完美结合

JSX(尤其是TSX)与TypeScript的集成是天衣无缝的。你可以获得完整的类型检查、自动补全和重构支持。

  • 组件Props类型定义:使用PropType或基于泛型的defineComponent来获得严格的props类型检查。
    import { defineComponent, PropType } from 'vue'; interface MyProps { title: string; count?: number; items: Array<{id: number; name: string}>; onCustomEvent: (value: string) => void; } export default defineComponent({ props: { title: { type: String, required: true }, count: Number, // 可选 items: Array as PropType<MyProps['items']>, // 复杂类型 onCustomEvent: Function as PropType<MyProps['onCustomEvent']>, }, setup(props) { // props.title 是 string 类型,有自动补全 // props.items 是 {id: number; name: string}[] 类型 props.onCustomEvent?.('hello'); // 类型安全的事件调用 // ... }, });
  • 渲染函数返回值类型:TypeScript能自动推断渲染函数的返回值类型。
  • 事件处理函数:事件处理函数的参数(如MouseEvent)和自定义事件payload都能获得完整的类型提示。

这大大减少了运行时错误,提升了大型项目的可维护性。在模板中,虽然Vue 3 +<script setup lang="ts">也能提供不错的类型支持,但在处理动态组件、高阶函数等复杂场景时,TSX的类型推导往往更加直接和强大。

5. 性能考量与最佳实践

虽然JSX带来了灵活性,但在Vue3中也需要考虑一些性能和习惯上的最佳实践。

5.1 避免不必要的渲染函数重建

setup中返回的渲染函数,如果其依赖的响应式数据没有变化,Vue不会重新执行它。但是,如果你在渲染函数内部创建了内联函数(比如事件处理器),每次渲染都会创建一个新的函数实例,这可能导致子组件不必要的更新。

// 欠佳:内联箭头函数 return () => ( <button onClick={() => doSomething(item.id)}>Click</button> ); // 更佳:使用 useMemo 或 稳定的引用 import { useMemo } from 'vue'; setup() { const handleClick = useMemo(() => (id: string) => { doSomething(id); }, []); // 依赖项为空,函数只创建一次 return () => ( <button onClick={() => handleClick(item.id)}>Click</button> // 或者,如果 item.id 是循环中的变量,可能需要更复杂的处理 ); }

对于列表渲染中的事件处理器,可以考虑将item作为参数传递给一个稳定的函数引用。

5.2 合理使用 Fragment 和 Teleport

JSX支持<>...</>(Fragment)语法来包裹多个相邻元素,而无需额外的DOM节点。

return () => ( <> {/* Fragment */} <header>标题</header> <main>内容</main> <footer>页脚</footer> </> );

对于<Teleport>,在JSX中可以直接使用。

import { Teleport } from 'vue'; return () => ( <div> <button>打开模态框</button> <Teleport to="body"> <div class="modal">模态框内容</div> </Teleport> </div> );

5.3 与 Vue 生态工具的结合

  • 状态管理(Pinia):在JSX组件中使用Pinia与在SFC中无异。在setup中调用useStore()即可。
    import { useUserStore } from '@/stores/user'; setup() { const userStore = useUserStore(); return () => <div>Hello, {userStore.name}</div>; }
  • 路由(Vue Router):使用useRouteruseRoute组合式API。
    import { useRouter, useRoute } from 'vue-router'; setup() { const router = useRouter(); const route = useRoute(); const goHome = () => router.push('/'); return () => ( <div> <p>当前路径: {route.path}</p> <button onClick={goHome}>首页</button> </div> ); }
  • UI 库(如 Element Plus, Ant Design Vue):大多数现代UI库都提供了良好的TypeScript支持和组合式API示例,在JSX中使用它们与在模板中类似,注意属性名改为小驼峰即可。查阅库文档中关于“在JSX中使用”的部分。

5.4 何时用JSX,何时用模板?

这是一个经验问题,没有绝对答案。我的个人准则是:

  • 优先使用模板:对于表现型组件(Presentational Components),即主要职责是渲染UI、结构相对固定、逻辑简单的组件。模板更声明式、更简洁,视觉结构一目了然,对于设计师和不太熟悉JavaScript的开发者更友好。.vue文件的<template><script><style>分离也符合关注点分离的习惯。
  • 考虑使用JSX/TSX
    1. 逻辑复杂型组件:渲染逻辑极度复杂,包含大量条件分支、循环嵌套或动态计算。
    2. 高阶组件/渲染函数组件:需要实现组件抽象、逻辑复用(如withLoadingwithTooltip)。
    3. 动态组件渲染:需要根据运行时数据动态决定组件类型或结构(如动态表单、可视化编辑器、JSON Schema渲染器)。
    4. 对TypeScript有极高要求:项目非常庞大,需要极致的类型安全和重构能力,JSX与TypeScript的集成在某些边缘场景下可能更顺畅。
    5. 团队技术栈偏好:如果团队成员主要来自React背景,或者非常熟悉JSX,那么统一使用JSX可以降低上下文切换成本。

一个项目中可以同时存在.vue.tsx文件。可以将复杂的、适合JSX的组件抽离成.tsx文件,而在业务页面或展示型组件中继续使用.vue模板。这种混合模式非常灵活。

6. 常见问题与排查技巧实录

在实际开发中,从模板切换到JSX可能会遇到一些特有的问题。这里记录了几个我踩过的坑和解决方案。

6.1 JSX 元素类型报错

问题:在TypeScript项目中,写<div>可能报错:JSX element type 'div' is not a constructor function for JSX elements.

原因:TypeScript无法正确识别Vue的JSX类型。这通常是因为tsconfig.json中缺少或配置了错误的JSX相关设置。

解决:确保tsconfig.jsoncompilerOptions包含以下配置:

{ "compilerOptions": { "jsx": "preserve", // 对于 Vue JSX,通常使用 "preserve" "jsxImportSource": "vue", // 告诉 TypeScript JSX 工厂函数来自 vue // 或者,如果你使用的是更老的配置,可能是: // "jsx": "preserve", // "jsxFactory": "h", // "jsxFragmentFactory": "Fragment" } }

具体配置可能因@vitejs/plugin-vue-jsx版本和Vue版本略有不同。最可靠的方法是参考你当前项目所用插件和Vue的官方文档。通常,使用Vite创建的项目已经配置好了。

6.2 事件处理器中访问响应式数据

问题:在JSX的事件处理函数中,直接访问ref.value,有时发现值不是最新的。

现象与原因

setup() { const count = ref(0); const handleClick = () => { // 如果handleClick是在渲染函数外定义的,且被用作回调(如传递给子组件) // 它捕获的是定义时的 count 值(闭包)。 console.log(count.value); }; return () => <button onClick={handleClick}>Click</button>; }

如果handleClicksetup中只定义了一次,它形成了一个闭包,捕获了初始的count引用。这通常没问题,因为count是一个ref,其.value属性是可变的。问题常出现在你handleClick作为prop传递给子组件,而子组件可能缓存了这个函数,导致其上下文不是最新的。但在大多数直接内联在JSX中的场景,每次渲染都会创建新函数,反而不容易出问题。

更常见的陷阱是解构

setup() { const state = reactive({ count: 0 }); // 错误!解构会失去响应性 const { count } = state; const handleClick = () => { console.log(count); // 永远输出 0 state.count++; // 修改 state.count,但 count 变量不会更新 }; return () => <button onClick={handleClick}>Click</button>; }

解决

  1. 始终通过ref.valuereactive对象本身来访问响应式数据。
  2. 如果需要在事件处理器中使用最新的值,且该处理器会被缓存,考虑使用computed或者确保在需要最新值时重新读取.value
  3. 对于reactive对象,避免在setup顶层直接解构其属性用于事件处理函数。如果需要解构并保持响应性,使用toRefs
    import { reactive, toRefs } from 'vue'; setup() { const state = reactive({ count: 0 }); const { count } = toRefs(state); // count 现在是一个 ref const handleClick = () => { console.log(count.value); // 正确 }; return () => <button onClick={handleClick}>Click</button>; }

6.3 样式和 Class 绑定

问题:在JSX中绑定classstyle,写法与HTML/模板有差异。

解决

  • class绑定

    • 静态类:<div class="container">
    • 动态类(对象语法):<div class={{ active: isActive, 'text-danger': hasError }}>
    • 动态类(数组语法):<div class={['base-class', isActive ? 'active' : '']}>
    • 混合:<div class={['static-class', { active: isActive }]}>

    注意:Vue的JSX插件通常期望使用class,而不是React中的className。但为了兼容,两者可能都支持,最好统一使用class

  • style绑定

    • 静态对象:<div style={{ color: 'red', fontSize: '14px' }}>(注意属性名是小驼峰:fontSize
    • 动态绑定:<div style={{ color: activeColor, fontSize: size + 'px' }}>
    • 数组语法(合并多个样式对象):Vue JSX也支持,但不如对象语法常用。

6.4 获取 DOM 引用

在模板中,我们使用ref属性配合ref响应式变量。在JSX中,用法几乎一样。

import { defineComponent, ref, onMounted } from 'vue'; export default defineComponent({ setup() { const inputRef = ref<HTMLInputElement | null>(null); onMounted(() => { if (inputRef.value) { inputRef.value.focus(); } }); return () => <input ref={inputRef} type="text" />; }, });

关键点:在JSX中,ref属性直接绑定到ref变量即可。类型标注为HTMLInputElement | null可以获得更好的TypeScript支持。

6.5 调试与开发工具

  • Vue Devtools:Vue Devtools 完全支持JSX组件。你可以在组件树中看到它们,检查它们的props、state和事件。确保你使用的是最新版的Vue Devtools。
  • 浏览器控制台:如果JSX编译或运行时出错,错误信息通常会指向你的.tsx文件行号。结合source map,可以方便地定位问题。
  • 类型检查:充分利用TypeScript的编译时检查。在VSCode等编辑器中,类型错误会直接标红,能在运行前发现许多潜在问题。

从模板转向JSX,初期最大的挑战是思维模式的转换。一旦你习惯了用JavaScript的函数式思维来描述UI,并且享受到了类型安全和灵活性的红利,在很多复杂场景下你会发现自己再也回不去了。我的建议是,不要试图全盘替换,而是在那些模板写起来开始感到“别扭”或“冗长”的组件中,尝试引入JSX。你会发现,Vue3提供的这种灵活性,让你能够为不同的任务选择最合适的工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询