1. 为什么选择TSXVue3开发的新思路第一次在Vue项目里看到TSX语法时我和很多开发者一样充满疑惑明明有现成的模板语法为什么还要用这种看起来像React的写法直到在一个大型后台管理系统项目中尝试迁移后我才真正体会到TSX的价值。TSX最大的优势在于类型安全和逻辑表达的自由度。在传统的单文件组件(SFC)中模板和逻辑是分离的这导致类型检查只能在script部分生效。而在TSX中整个组件就是一个TypeScript函数模板部分也能享受完整的类型提示。比如下面这个简单的用户列表组件interface User { id: number name: string avatar: string } const UserList defineComponent({ setup() { const users refUser[]([ { id: 1, name: 张三, avatar: /avatar1.jpg }, { id: 2, name: 李四, avatar: /avatar2.jpg } ]) return () ( div classuser-list {users.value.map(user ( UserItem key{user.id} name{user.name} avatar{user.avatar} / ))} /div ) } })在这个例子中我们定义了一个User接口然后在组件中使用时无论是users数组还是UserItem组件的props都能获得完整的类型检查。如果尝试传递一个不符合User类型的对象TypeScript会在编译时就报错而不是等到运行时才发现问题。另一个实际优势是逻辑与视图的更好结合。在复杂组件中经常需要根据数据状态决定渲染内容。在SFC中这通常需要在模板中使用大量v-if/v-else或者在script中定义复杂的计算属性。而在TSX中你可以直接用JavaScript表达式const ComplexComponent defineComponent({ setup() { const state reactive({ isLoading: true, data: null as DataType | null, error: null as Error | null }) fetchData().then( data { state.data data state.isLoading false }, error { state.error error state.isLoading false } ) return () { if (state.isLoading) { return LoadingSpinner / } if (state.error) { return ErrorMessage error{state.error} / } return DataDisplay data{state.data!} / } } })这种写法让组件的逻辑流更加清晰不需要在模板和script之间来回跳转就能理解整个组件的运行逻辑。特别是在处理异步数据时TSX的表达能力明显优于模板语法。2. 模板指令的TSX迁移指南2.1 条件渲染从v-if到JSX表达式在模板语法中我们习惯使用v-if/v-else来做条件渲染。但在TSX中v-if指令是不支持的需要改用JavaScript的条件表达式。这看起来是个限制实际上却提供了更灵活的渲染控制方式。最简单的替代方案是三元表达式。比如原来这样的模板template div p v-ifisAdmin管理员面板/p p v-else普通用户面板/p /div /template在TSX中可以改写为const UserPanel defineComponent({ setup() { const isAdmin ref(false) return () ( div {isAdmin.value ? p管理员面板/p : p普通用户面板/p } /div ) } })对于更复杂的条件逻辑可以使用立即执行函数(IIFE)const ComplexCondition defineComponent({ setup() { const user reactive({ role: editor, status: active }) return () ( div {(() { if (user.role admin) { return AdminPanel / } else if (user.role editor user.status active) { return EditorPanel / } else { return GuestPanel / } })()} /div ) } })这种方式虽然看起来比v-if冗长但在处理复杂条件时实际上更清晰特别是当条件分支很多时IIFE的结构比一连串的v-if/v-else-if更容易维护。2.2 列表渲染从v-for到数组mapv-for是Vue模板中最常用的指令之一在TSX中我们需要用JavaScript的数组map方法来替代。这种转换不仅更符合JavaScript的惯用法还能更好地利用TypeScript的类型系统。一个常见的用户列表例子const UserList defineComponent({ setup() { const users ref([ { id: 1, name: Alice }, { id: 2, name: Bob } ]) return () ( ul {users.value.map(user ( li key{user.id}{user.name}/li ))} /ul ) } })这里有几个需要注意的点key属性和v-for一样列表项仍然需要唯一的key只是现在作为普通的JSX属性传递类型推断如果users有明确的类型定义map回调中的user参数会自动获得正确的类型提示复杂结构可以在map回调中返回任意JSX结构不受模板语法的限制对于需要同时访问索引的情况map回调的第二个参数就是当前项的索引{items.value.map((item, index) ( div key{item.id} span#{index 1}/span span{item.name}/span /div ))}2.3 显示/隐藏控制v-show的TSX实现v-show在TSX中的实现是最简单的因为它本质上就是一个style.display的切换。在TSX中我们可以直接用JavaScript的逻辑与()运算符来实现类似效果const ToggleMessage defineComponent({ setup() { const isVisible ref(false) return () ( div button onClick{() isVisible.value !isVisible.value} 切换显示 /button {isVisible.value p这段文字会显示/隐藏/p} /div ) } })注意这和v-if的区别当条件为false时JSX的运算符不会渲染元素相当于v-if而v-show总是会渲染元素只是切换display样式。如果需要完全模拟v-show的行为可以这样写p style{{ display: isVisible.value ? block : none }} 这段文字会通过display属性显示/隐藏 /p2.4 属性绑定v-bind的替代方案在TSX中v-bind指令不再需要所有属性都通过JSX的属性语法直接绑定。这实际上让代码更加简洁明了。静态属性和动态属性的对比const ImageComponent defineComponent({ setup() { const imageUrl ref(/default.jpg) const altText ref(默认图片) return () ( div {/* 静态属性 */} img src/static/logo.png altLogo / {/* 动态属性 */} img src{imageUrl.value} alt{altText.value} classresponsive-image / /div ) } })对于需要绑定多个属性的情况可以使用展开运算符const user reactive({ id: 1, name: 张三, avatar: /avatar1.jpg, role: admin }) return () ( UserProfile {...user} / )这相当于把user对象的每个属性都作为单独的prop传递给UserProfile组件非常简洁高效。2.5 事件处理从v-on到JSX事件事件处理是交互式组件的核心功能在TSX中事件监听器的写法与React类似使用on事件名的驼峰形式。基本的事件绑定const ClickDemo defineComponent({ setup() { const handleClick (event: MouseEvent) { console.log(点击事件, event) } return () ( button onClick{handleClick}点击我/button ) } })如果需要传递额外参数可以使用箭头函数或bind方法const ListItem defineComponent({ setup() { const items ref([苹果, 香蕉, 橙子]) const handleItemClick (item: string, index: number, event: MouseEvent) { console.log(点击了第${index 1}项: ${item}, event) } return () ( ul {items.value.map((item, index) ( li key{index} onClick{(e) handleItemClick(item, index, e)} {item} /li ))} /ul ) } })需要注意的是TSX中不支持Vue模板的事件修饰符如.stop、.prevent等。这些功能需要手动实现const handleSubmit (event: MouseEvent) { event.preventDefault() event.stopPropagation() // 提交逻辑 } return () ( form onSubmit{handleSubmit} {/* 表单内容 */} /form )3. 组件通信的TSX实现3.1 Props父子组件数据传递在TSX中定义props时我们可以充分利用TypeScript的类型系统来明确组件接口。这是TSX相比模板语法的一大优势。定义带props的组件interface ButtonProps { type?: primary | default | danger size?: small | medium | large disabled?: boolean onClick?: (event: MouseEvent) void } const Button defineComponent({ props: { type: { type: String as PropTypeButtonProps[type], default: default }, size: { type: String as PropTypeButtonProps[size], default: medium }, disabled: Boolean, onClick: Function as PropTypeButtonProps[onClick] }, setup(props) { return () ( button class{[ btn, btn-${props.type}, btn-${props.size} ]} disabled{props.disabled} onClick{props.onClick} slot / /button ) } })使用这个Button组件时TypeScript会检查传入的props是否符合定义的类型const App defineComponent({ setup() { const handleClick () console.log(按钮点击) return () ( div {/* 正确的使用方式 */} Button typeprimary onClick{handleClick} 提交 /Button {/* TypeScript会报错type只能是特定值 */} Button typewarning 警告 /Button /div ) } })3.2 自定义事件子到父通信在TSX中发射自定义事件与模板语法有些不同我们需要使用setup函数的第二个参数context来访问emit方法。子组件发射事件interface Emits { (e: update:value, value: string): void (e: submit, payload: { value: string; isValid: boolean }): void } const SearchInput defineComponent({ emits: { update:value: (value: string) typeof value string, submit: (payload: { value: string; isValid: boolean }) typeof payload.value string typeof payload.isValid boolean }, setup(props, { emit }) { const inputValue ref() const handleInput (e: Event) { const value (e.target as HTMLInputElement).value inputValue.value value emit(update:value, value) } const handleSubmit () { emit(submit, { value: inputValue.value, isValid: inputValue.value.length 0 }) } return () ( div classsearch-box input typetext value{inputValue.value} onInput{handleInput} / button onClick{handleSubmit} 搜索 /button /div ) } })父组件监听事件const SearchPage defineComponent({ setup() { const searchValue ref() const handleValueUpdate (value: string) { searchValue.value value } const handleSearch (payload: { value: string; isValid: boolean }) { if (payload.isValid) { fetchResults(payload.value) } } return () ( div h1搜索页面/h1 SearchInput onUpdate:value{handleValueUpdate} onSubmit{handleSearch} / {/* 搜索结果列表 */} /div ) } })3.3 插槽灵活的内容分发TSX中的插槽实现与模板语法有所不同但同样强大。Vue 3在setup函数中提供了slots对象来访问插槽内容。基本插槽示例const Card defineComponent({ setup(props, { slots }) { return () ( div classcard {slots.default?.()} /div ) } }) // 使用 const App defineComponent({ setup() { return () ( Card h2卡片标题/h2 p卡片内容/p /Card ) } })具名插槽和作用域插槽const UserProfile defineComponent({ setup(props, { slots }) { const user reactive({ name: 张三, age: 28, role: developer }) return () ( div classprofile {slots.header?.()} div classprofile-content {slots.default?.({ user, isAdmin: user.role admin })} /div {slots.footer?.()} /div ) } }) // 使用 const App defineComponent({ setup() { return () ( UserProfile {{ header: () h2用户信息/h2, default: ({ user, isAdmin }) ( div p姓名: {user.name}/p p年龄: {user.age}/p {isAdmin p管理员权限/p} /div ), footer: () div classactions button编辑/button /div }} /UserProfile ) } })4. 迁移实战一个完整组件的TSX重构让我们通过一个实际的例子将一个使用模板语法的Vue组件逐步重构为TSX实现。这是一个任务列表组件包含以下功能显示任务列表支持任务筛选可以标记任务完成状态可以删除任务4.1 原始模板实现template div classtask-manager div classfilters button v-forfilter in filters :keyfilter clickcurrentFilter filter :class{ active: currentFilter filter } {{ filter }} /button /div ul classtask-list li v-fortask in filteredTasks :keytask.id :class{ completed: task.completed } input typecheckbox v-modeltask.completed / span{{ task.text }}/span button clickremoveTask(task.id) 删除 /button /li /ul div classadd-task input v-modelnewTaskText keyup.enteraddTask placeholder添加新任务 / button clickaddTask 添加 /button /div /div /template script setup langts import { ref, computed } from vue interface Task { id: number text: string completed: boolean } const filters [全部, 进行中, 已完成] as const type Filter typeof filters[number] const tasks refTask[]([ { id: 1, text: 学习Vue 3, completed: false }, { id: 2, text: 迁移项目到TSX, completed: true } ]) const newTaskText ref() const currentFilter refFilter(全部) const filteredTasks computed(() { switch (currentFilter.value) { case 进行中: return tasks.value.filter(t !t.completed) case 已完成: return tasks.value.filter(t t.completed) default: return tasks.value } }) function addTask() { if (!newTaskText.value.trim()) return tasks.value.push({ id: Date.now(), text: newTaskText.value.trim(), completed: false }) newTaskText.value } function removeTask(id: number) { tasks.value tasks.value.filter(t t.id ! id) } /script4.2 TSX重构步骤第一步创建组件骨架import { defineComponent, ref, computed } from vue interface Task { id: number text: string completed: boolean } const filters [全部, 进行中, 已完成] as const type Filter typeof filters[number] const TaskManager defineComponent({ setup() { // 状态定义 const tasks refTask[]([ { id: 1, text: 学习Vue 3, completed: false }, { id: 2, text: 迁移项目到TSX, completed: true } ]) const newTaskText ref() const currentFilter refFilter(全部) // 计算属性 const filteredTasks computed(() { switch (currentFilter.value) { case 进行中: return tasks.value.filter(t !t.completed) case 已完成: return tasks.value.filter(t t.completed) default: return tasks.value } }) // 方法 const addTask () { if (!newTaskText.value.trim()) return tasks.value.push({ id: Date.now(), text: newTaskText.value.trim(), completed: false }) newTaskText.value } const removeTask (id: number) { tasks.value tasks.value.filter(t t.id ! id) } // 返回渲染函数 return () ( div classtask-manager {/* 这里将填充JSX内容 */} /div ) } }) export default TaskManager第二步实现过滤器按钮// 在返回的JSX中添加 div classfilters {filters.map(filter ( button key{filter} class{{ active: currentFilter.value filter }} onClick{() currentFilter.value filter} {filter} /button ))} /div第三步实现任务列表ul classtask-list {filteredTasks.value.map(task ( li key{task.id} class{{ completed: task.completed }} input typecheckbox checked{task.completed} onChange{() task.completed !task.completed} / span{task.text}/span button onClick{() removeTask(task.id)} 删除 /button /li ))} /ul第四步实现添加任务功能div classadd-task input value{newTaskText.value} onInput{(e) newTaskText.value (e.target as HTMLInputElement).value} onKeyup{(e) e.key Enter addTask()} placeholder添加新任务 / button onClick{addTask} 添加 /button /div4.3 完整TSX实现import { defineComponent, ref, computed } from vue interface Task { id: number text: string completed: boolean } const filters [全部, 进行中, 已完成] as const type Filter typeof filters[number] const TaskManager defineComponent({ setup() { // 状态定义 const tasks refTask[]([ { id: 1, text: 学习Vue 3, completed: false }, { id: 2, text: 迁移项目到TSX, completed: true } ]) const newTaskText ref() const currentFilter refFilter(全部) // 计算属性 const filteredTasks computed(() { switch (currentFilter.value) { case 进行中: return tasks.value.filter(t !t.completed) case 已完成: return tasks.value.filter(t t.completed) default: return tasks.value } }) // 方法 const addTask () { if (!newTaskText.value.trim()) return tasks.value.push({ id: Date.now(), text: newTaskText.value.trim(), completed: false }) newTaskText.value } const removeTask (id: number) { tasks.value tasks.value.filter(t t.id ! id) } // 返回渲染函数 return () ( div classtask-manager div classfilters {filters.map(filter ( button key{filter} class{{ active: currentFilter.value filter }} onClick{() currentFilter.value filter} {filter} /button ))} /div ul classtask-list {filteredTasks.value.map(task ( li key{task.id} class{{ completed: task.completed }} input typecheckbox checked{task.completed} onChange{() task.completed !task.completed} / span{task.text}/span button onClick{() removeTask(task.id)} 删除 /button /li ))} /ul div classadd-task input value{newTaskText.value} onInput{(e) newTaskText.value (e.target as HTMLInputElement).value} onKeyup{(e) e.key Enter addTask()} placeholder添加新任务 / button onClick{addTask} 添加 /button /div /div ) } }) export default TaskManager4.4 迁移后的优势分析类型安全整个组件现在都有完整的类型检查包括props、事件、模板表达式等逻辑组织更灵活不再受限于模板和script的分离可以把相关逻辑组织在一起更好的IDE支持JSX在TypeScript环境中有更好的自动完成和错误检查复用性提升渲染逻辑可以更容易地提取为独立的函数或自定义Hook更一致的编码风格整个项目可以统一使用TypeScript的语法和工具链在实际项目中从模板迁移到TSX可能会遇到一些挑战特别是对于大型复杂组件。建议采取渐进式迁移策略先从简单的展示组件开始逐步过渡到复杂的交互组件。
Vue3 TSX实战:从模板指令到组件通信的完整迁移指南
发布时间:2026/6/30 11:14:48
1. 为什么选择TSXVue3开发的新思路第一次在Vue项目里看到TSX语法时我和很多开发者一样充满疑惑明明有现成的模板语法为什么还要用这种看起来像React的写法直到在一个大型后台管理系统项目中尝试迁移后我才真正体会到TSX的价值。TSX最大的优势在于类型安全和逻辑表达的自由度。在传统的单文件组件(SFC)中模板和逻辑是分离的这导致类型检查只能在script部分生效。而在TSX中整个组件就是一个TypeScript函数模板部分也能享受完整的类型提示。比如下面这个简单的用户列表组件interface User { id: number name: string avatar: string } const UserList defineComponent({ setup() { const users refUser[]([ { id: 1, name: 张三, avatar: /avatar1.jpg }, { id: 2, name: 李四, avatar: /avatar2.jpg } ]) return () ( div classuser-list {users.value.map(user ( UserItem key{user.id} name{user.name} avatar{user.avatar} / ))} /div ) } })在这个例子中我们定义了一个User接口然后在组件中使用时无论是users数组还是UserItem组件的props都能获得完整的类型检查。如果尝试传递一个不符合User类型的对象TypeScript会在编译时就报错而不是等到运行时才发现问题。另一个实际优势是逻辑与视图的更好结合。在复杂组件中经常需要根据数据状态决定渲染内容。在SFC中这通常需要在模板中使用大量v-if/v-else或者在script中定义复杂的计算属性。而在TSX中你可以直接用JavaScript表达式const ComplexComponent defineComponent({ setup() { const state reactive({ isLoading: true, data: null as DataType | null, error: null as Error | null }) fetchData().then( data { state.data data state.isLoading false }, error { state.error error state.isLoading false } ) return () { if (state.isLoading) { return LoadingSpinner / } if (state.error) { return ErrorMessage error{state.error} / } return DataDisplay data{state.data!} / } } })这种写法让组件的逻辑流更加清晰不需要在模板和script之间来回跳转就能理解整个组件的运行逻辑。特别是在处理异步数据时TSX的表达能力明显优于模板语法。2. 模板指令的TSX迁移指南2.1 条件渲染从v-if到JSX表达式在模板语法中我们习惯使用v-if/v-else来做条件渲染。但在TSX中v-if指令是不支持的需要改用JavaScript的条件表达式。这看起来是个限制实际上却提供了更灵活的渲染控制方式。最简单的替代方案是三元表达式。比如原来这样的模板template div p v-ifisAdmin管理员面板/p p v-else普通用户面板/p /div /template在TSX中可以改写为const UserPanel defineComponent({ setup() { const isAdmin ref(false) return () ( div {isAdmin.value ? p管理员面板/p : p普通用户面板/p } /div ) } })对于更复杂的条件逻辑可以使用立即执行函数(IIFE)const ComplexCondition defineComponent({ setup() { const user reactive({ role: editor, status: active }) return () ( div {(() { if (user.role admin) { return AdminPanel / } else if (user.role editor user.status active) { return EditorPanel / } else { return GuestPanel / } })()} /div ) } })这种方式虽然看起来比v-if冗长但在处理复杂条件时实际上更清晰特别是当条件分支很多时IIFE的结构比一连串的v-if/v-else-if更容易维护。2.2 列表渲染从v-for到数组mapv-for是Vue模板中最常用的指令之一在TSX中我们需要用JavaScript的数组map方法来替代。这种转换不仅更符合JavaScript的惯用法还能更好地利用TypeScript的类型系统。一个常见的用户列表例子const UserList defineComponent({ setup() { const users ref([ { id: 1, name: Alice }, { id: 2, name: Bob } ]) return () ( ul {users.value.map(user ( li key{user.id}{user.name}/li ))} /ul ) } })这里有几个需要注意的点key属性和v-for一样列表项仍然需要唯一的key只是现在作为普通的JSX属性传递类型推断如果users有明确的类型定义map回调中的user参数会自动获得正确的类型提示复杂结构可以在map回调中返回任意JSX结构不受模板语法的限制对于需要同时访问索引的情况map回调的第二个参数就是当前项的索引{items.value.map((item, index) ( div key{item.id} span#{index 1}/span span{item.name}/span /div ))}2.3 显示/隐藏控制v-show的TSX实现v-show在TSX中的实现是最简单的因为它本质上就是一个style.display的切换。在TSX中我们可以直接用JavaScript的逻辑与()运算符来实现类似效果const ToggleMessage defineComponent({ setup() { const isVisible ref(false) return () ( div button onClick{() isVisible.value !isVisible.value} 切换显示 /button {isVisible.value p这段文字会显示/隐藏/p} /div ) } })注意这和v-if的区别当条件为false时JSX的运算符不会渲染元素相当于v-if而v-show总是会渲染元素只是切换display样式。如果需要完全模拟v-show的行为可以这样写p style{{ display: isVisible.value ? block : none }} 这段文字会通过display属性显示/隐藏 /p2.4 属性绑定v-bind的替代方案在TSX中v-bind指令不再需要所有属性都通过JSX的属性语法直接绑定。这实际上让代码更加简洁明了。静态属性和动态属性的对比const ImageComponent defineComponent({ setup() { const imageUrl ref(/default.jpg) const altText ref(默认图片) return () ( div {/* 静态属性 */} img src/static/logo.png altLogo / {/* 动态属性 */} img src{imageUrl.value} alt{altText.value} classresponsive-image / /div ) } })对于需要绑定多个属性的情况可以使用展开运算符const user reactive({ id: 1, name: 张三, avatar: /avatar1.jpg, role: admin }) return () ( UserProfile {...user} / )这相当于把user对象的每个属性都作为单独的prop传递给UserProfile组件非常简洁高效。2.5 事件处理从v-on到JSX事件事件处理是交互式组件的核心功能在TSX中事件监听器的写法与React类似使用on事件名的驼峰形式。基本的事件绑定const ClickDemo defineComponent({ setup() { const handleClick (event: MouseEvent) { console.log(点击事件, event) } return () ( button onClick{handleClick}点击我/button ) } })如果需要传递额外参数可以使用箭头函数或bind方法const ListItem defineComponent({ setup() { const items ref([苹果, 香蕉, 橙子]) const handleItemClick (item: string, index: number, event: MouseEvent) { console.log(点击了第${index 1}项: ${item}, event) } return () ( ul {items.value.map((item, index) ( li key{index} onClick{(e) handleItemClick(item, index, e)} {item} /li ))} /ul ) } })需要注意的是TSX中不支持Vue模板的事件修饰符如.stop、.prevent等。这些功能需要手动实现const handleSubmit (event: MouseEvent) { event.preventDefault() event.stopPropagation() // 提交逻辑 } return () ( form onSubmit{handleSubmit} {/* 表单内容 */} /form )3. 组件通信的TSX实现3.1 Props父子组件数据传递在TSX中定义props时我们可以充分利用TypeScript的类型系统来明确组件接口。这是TSX相比模板语法的一大优势。定义带props的组件interface ButtonProps { type?: primary | default | danger size?: small | medium | large disabled?: boolean onClick?: (event: MouseEvent) void } const Button defineComponent({ props: { type: { type: String as PropTypeButtonProps[type], default: default }, size: { type: String as PropTypeButtonProps[size], default: medium }, disabled: Boolean, onClick: Function as PropTypeButtonProps[onClick] }, setup(props) { return () ( button class{[ btn, btn-${props.type}, btn-${props.size} ]} disabled{props.disabled} onClick{props.onClick} slot / /button ) } })使用这个Button组件时TypeScript会检查传入的props是否符合定义的类型const App defineComponent({ setup() { const handleClick () console.log(按钮点击) return () ( div {/* 正确的使用方式 */} Button typeprimary onClick{handleClick} 提交 /Button {/* TypeScript会报错type只能是特定值 */} Button typewarning 警告 /Button /div ) } })3.2 自定义事件子到父通信在TSX中发射自定义事件与模板语法有些不同我们需要使用setup函数的第二个参数context来访问emit方法。子组件发射事件interface Emits { (e: update:value, value: string): void (e: submit, payload: { value: string; isValid: boolean }): void } const SearchInput defineComponent({ emits: { update:value: (value: string) typeof value string, submit: (payload: { value: string; isValid: boolean }) typeof payload.value string typeof payload.isValid boolean }, setup(props, { emit }) { const inputValue ref() const handleInput (e: Event) { const value (e.target as HTMLInputElement).value inputValue.value value emit(update:value, value) } const handleSubmit () { emit(submit, { value: inputValue.value, isValid: inputValue.value.length 0 }) } return () ( div classsearch-box input typetext value{inputValue.value} onInput{handleInput} / button onClick{handleSubmit} 搜索 /button /div ) } })父组件监听事件const SearchPage defineComponent({ setup() { const searchValue ref() const handleValueUpdate (value: string) { searchValue.value value } const handleSearch (payload: { value: string; isValid: boolean }) { if (payload.isValid) { fetchResults(payload.value) } } return () ( div h1搜索页面/h1 SearchInput onUpdate:value{handleValueUpdate} onSubmit{handleSearch} / {/* 搜索结果列表 */} /div ) } })3.3 插槽灵活的内容分发TSX中的插槽实现与模板语法有所不同但同样强大。Vue 3在setup函数中提供了slots对象来访问插槽内容。基本插槽示例const Card defineComponent({ setup(props, { slots }) { return () ( div classcard {slots.default?.()} /div ) } }) // 使用 const App defineComponent({ setup() { return () ( Card h2卡片标题/h2 p卡片内容/p /Card ) } })具名插槽和作用域插槽const UserProfile defineComponent({ setup(props, { slots }) { const user reactive({ name: 张三, age: 28, role: developer }) return () ( div classprofile {slots.header?.()} div classprofile-content {slots.default?.({ user, isAdmin: user.role admin })} /div {slots.footer?.()} /div ) } }) // 使用 const App defineComponent({ setup() { return () ( UserProfile {{ header: () h2用户信息/h2, default: ({ user, isAdmin }) ( div p姓名: {user.name}/p p年龄: {user.age}/p {isAdmin p管理员权限/p} /div ), footer: () div classactions button编辑/button /div }} /UserProfile ) } })4. 迁移实战一个完整组件的TSX重构让我们通过一个实际的例子将一个使用模板语法的Vue组件逐步重构为TSX实现。这是一个任务列表组件包含以下功能显示任务列表支持任务筛选可以标记任务完成状态可以删除任务4.1 原始模板实现template div classtask-manager div classfilters button v-forfilter in filters :keyfilter clickcurrentFilter filter :class{ active: currentFilter filter } {{ filter }} /button /div ul classtask-list li v-fortask in filteredTasks :keytask.id :class{ completed: task.completed } input typecheckbox v-modeltask.completed / span{{ task.text }}/span button clickremoveTask(task.id) 删除 /button /li /ul div classadd-task input v-modelnewTaskText keyup.enteraddTask placeholder添加新任务 / button clickaddTask 添加 /button /div /div /template script setup langts import { ref, computed } from vue interface Task { id: number text: string completed: boolean } const filters [全部, 进行中, 已完成] as const type Filter typeof filters[number] const tasks refTask[]([ { id: 1, text: 学习Vue 3, completed: false }, { id: 2, text: 迁移项目到TSX, completed: true } ]) const newTaskText ref() const currentFilter refFilter(全部) const filteredTasks computed(() { switch (currentFilter.value) { case 进行中: return tasks.value.filter(t !t.completed) case 已完成: return tasks.value.filter(t t.completed) default: return tasks.value } }) function addTask() { if (!newTaskText.value.trim()) return tasks.value.push({ id: Date.now(), text: newTaskText.value.trim(), completed: false }) newTaskText.value } function removeTask(id: number) { tasks.value tasks.value.filter(t t.id ! id) } /script4.2 TSX重构步骤第一步创建组件骨架import { defineComponent, ref, computed } from vue interface Task { id: number text: string completed: boolean } const filters [全部, 进行中, 已完成] as const type Filter typeof filters[number] const TaskManager defineComponent({ setup() { // 状态定义 const tasks refTask[]([ { id: 1, text: 学习Vue 3, completed: false }, { id: 2, text: 迁移项目到TSX, completed: true } ]) const newTaskText ref() const currentFilter refFilter(全部) // 计算属性 const filteredTasks computed(() { switch (currentFilter.value) { case 进行中: return tasks.value.filter(t !t.completed) case 已完成: return tasks.value.filter(t t.completed) default: return tasks.value } }) // 方法 const addTask () { if (!newTaskText.value.trim()) return tasks.value.push({ id: Date.now(), text: newTaskText.value.trim(), completed: false }) newTaskText.value } const removeTask (id: number) { tasks.value tasks.value.filter(t t.id ! id) } // 返回渲染函数 return () ( div classtask-manager {/* 这里将填充JSX内容 */} /div ) } }) export default TaskManager第二步实现过滤器按钮// 在返回的JSX中添加 div classfilters {filters.map(filter ( button key{filter} class{{ active: currentFilter.value filter }} onClick{() currentFilter.value filter} {filter} /button ))} /div第三步实现任务列表ul classtask-list {filteredTasks.value.map(task ( li key{task.id} class{{ completed: task.completed }} input typecheckbox checked{task.completed} onChange{() task.completed !task.completed} / span{task.text}/span button onClick{() removeTask(task.id)} 删除 /button /li ))} /ul第四步实现添加任务功能div classadd-task input value{newTaskText.value} onInput{(e) newTaskText.value (e.target as HTMLInputElement).value} onKeyup{(e) e.key Enter addTask()} placeholder添加新任务 / button onClick{addTask} 添加 /button /div4.3 完整TSX实现import { defineComponent, ref, computed } from vue interface Task { id: number text: string completed: boolean } const filters [全部, 进行中, 已完成] as const type Filter typeof filters[number] const TaskManager defineComponent({ setup() { // 状态定义 const tasks refTask[]([ { id: 1, text: 学习Vue 3, completed: false }, { id: 2, text: 迁移项目到TSX, completed: true } ]) const newTaskText ref() const currentFilter refFilter(全部) // 计算属性 const filteredTasks computed(() { switch (currentFilter.value) { case 进行中: return tasks.value.filter(t !t.completed) case 已完成: return tasks.value.filter(t t.completed) default: return tasks.value } }) // 方法 const addTask () { if (!newTaskText.value.trim()) return tasks.value.push({ id: Date.now(), text: newTaskText.value.trim(), completed: false }) newTaskText.value } const removeTask (id: number) { tasks.value tasks.value.filter(t t.id ! id) } // 返回渲染函数 return () ( div classtask-manager div classfilters {filters.map(filter ( button key{filter} class{{ active: currentFilter.value filter }} onClick{() currentFilter.value filter} {filter} /button ))} /div ul classtask-list {filteredTasks.value.map(task ( li key{task.id} class{{ completed: task.completed }} input typecheckbox checked{task.completed} onChange{() task.completed !task.completed} / span{task.text}/span button onClick{() removeTask(task.id)} 删除 /button /li ))} /ul div classadd-task input value{newTaskText.value} onInput{(e) newTaskText.value (e.target as HTMLInputElement).value} onKeyup{(e) e.key Enter addTask()} placeholder添加新任务 / button onClick{addTask} 添加 /button /div /div ) } }) export default TaskManager4.4 迁移后的优势分析类型安全整个组件现在都有完整的类型检查包括props、事件、模板表达式等逻辑组织更灵活不再受限于模板和script的分离可以把相关逻辑组织在一起更好的IDE支持JSX在TypeScript环境中有更好的自动完成和错误检查复用性提升渲染逻辑可以更容易地提取为独立的函数或自定义Hook更一致的编码风格整个项目可以统一使用TypeScript的语法和工具链在实际项目中从模板迁移到TSX可能会遇到一些挑战特别是对于大型复杂组件。建议采取渐进式迁移策略先从简单的展示组件开始逐步过渡到复杂的交互组件。