文章目录
- 一、基础环境配置差异
- 1.1 项目创建对比
- 1.2 TS专属配置:tsconfig.json核心参数
- 二、组件定义核心语法差异
- 2.1 <script setup>语法对比
- JavaScript写法(简洁无类型)
- TypeScript写法(类型明确)
- 2.2 选项式API对比(传统风格)
- JavaScript写法
- TypeScript写法(需defineComponent)
- 三、响应式API写法差异
- 3.1 ref API对比(基础类型/对象类型)
- 3.2 reactive API对比(复杂对象)
- JavaScript写法
- TypeScript写法
- 3.3 computed API对比(计算属性)
- JavaScript写法
- TypeScript写法
- 四、Props/Emits类型约束差异
- 4.1 Props定义对比
- JavaScript写法(运行时校验)
- TypeScript写法(编译时校验)
- 4.2 Emits定义对比
- JavaScript写法(仅声明事件名)
- TypeScript写法(事件参数类型约束)
- 五、生命周期钩子用法差异
- 5.1 基础生命周期对比
- JavaScript写法
- TypeScript写法
- 5.2 自定义Hook中的生命周期
- JavaScript写法(useTimer.js)
- TypeScript写法(useTimer.ts)
- 六、Vue Router集成差异
- 6.1 路由配置对比
- JavaScript写法(router/index.js)
- TypeScript写法(router/index.ts)
- 6.2 组件内路由使用对比
- JavaScript写法
- TypeScript写法(带类型推导)
- 七、Pinia状态管理差异
- 7.1 Store定义对比
- JavaScript写法(useUserStore.js)
- TypeScript写法(useUserStore.ts)
- 7.2 Store使用对比
- JavaScript写法
- TypeScript写法
- 八、自定义Hook封装差异
- 8.1 数据请求Hook对比(useRequest)
- JavaScript写法(useRequest.js)
- TypeScript写法(useRequest.ts)
- 九、异步请求与事件处理差异
- 9.1 异步请求对比(Axios)
- JavaScript写法
- TypeScript写法
- 9.2 DOM事件处理对比
- JavaScript写法
- TypeScript写法
- 十、TS专属类型工具与语法
- 10.1 基础类型工具
- 10.2 Vue3开发高频TS语法
- 十一、核心差异总结表
一、基础环境配置差异
Vue3项目中JS与TS的环境差异体现在项目创建、配置文件、依赖包三个层面,TS需要额外的类型配置来实现语法校验。
1.1 项目创建对比
| 配置项 | JavaScript写法 | TypeScript写法 |
|---|---|---|
| 创建命令 | npm create vue@latest→ 取消勾选“TypeScript” | npm create vue@latest→ 勾选“TypeScript” |
| 核心文件 | 组件后缀:.vue;配置文件:vite.config.js | 组件后缀:.vue(内部指定lang="ts");配置文件:vite.config.ts、tsconfig.json |
| 依赖包 | 仅需Vue核心依赖:vue、@vitejs/plugin-vue | 额外依赖:typescript、@vue/tsconfig、vue-tsc(类型检查) |
1.2 TS专属配置:tsconfig.json核心参数
TS项目必须通过tsconfig.json指定类型规则,核心配置如下(基于Vue官方推荐模板):
{"compilerOptions":{"target":"ES2020",// 编译目标ES版本"strict":true,// 开启严格类型校验(核心)"module":"ESNext","moduleResolution":"Node","isolatedModules":true,// 适配Vite单文件转译"jsx":"preserve",// 支持TSX"sourceMap":true,"paths":{"@/*":["./src/*"]},// 路径别名(与Vite同步)"lib":["ES2020","DOM"],// 引入浏览器环境类型"skipLibCheck":true},"include":["src/**/*.ts","src/**/*.vue"],// 类型检查范围"references":[{"path":"./tsconfig.node.json"}]}关键说明:strict: true是TS类型安全的核心,会强制校验this指向、空值判断等,避免隐式any类型。
二、组件定义核心语法差异
Vue3组件定义分为<script setup>(推荐)和选项式API两种风格,TS与JS的差异集中在类型声明和语法约束上,模板部分完全一致。
2.1 <script setup>语法对比
JavaScript写法(简洁无类型)
<template><divclass="user-card"><h3>{{user.name}}</h3><button @click="handleEdit">编辑</button></div></template><script setup>// 无类型约束,直接声明变量constuser={name:'张三',age:24};// 函数参数与返回值无类型consthandleEdit=(newName)=>{user.name=newName||user.name;};// 导入组件无需类型处理importAvatarfrom'./Avatar.vue';</script>TypeScript写法(类型明确)
<template><divclass="user-card"><h3>{{user.name}}</h3><button @click="handleEdit('李四')">编辑</button></div></template><script setup lang="ts">// 1. 接口定义复杂类型(TS专属)interfaceUser{name:string;age:number;gender?:string;// 可选属性}// 2. 变量显式类型约束constuser:User={name:'张三',age:24};// 3. 函数参数与返回值类型声明consthandleEdit=(newName:string):void=>{user.name=newName;};// 4. 组件类型获取(TS专属)importAvatarfrom'./Avatar.vue';importtype{ComponentPublicInstance}from'vue';typeAvatarInstance=ComponentPublicInstance<typeofAvatar>;// 组件实例类型</script>2.2 选项式API对比(传统风格)
JavaScript写法
<script>exportdefault{name:'UserCard',data(){return{user:{name:'张三',age:24}};},methods:{handleEdit(newName){this.user.name=newName;}}};</script>TypeScript写法(需defineComponent)
<script lang="ts">import{defineComponent}from'vue';interfaceUser{name:string;age:number;}exportdefaultdefineComponent({name:'UserCard',// TS自动推导data返回值类型data(){return{user:{name:'张三',age:24}asUser// 类型断言};},methods:{// 方法参数与返回值类型约束handleEdit(newName:string):void{this.user.name=newName;// this指向被TS正确推导}}});</script>关键差异:TS的defineComponent函数是类型推断的核心,能自动解析组件选项中的this类型,避免JS中this隐式为any的问题。
三、响应式API写法差异
Vue3的响应式API(ref、reactive、computed)是开发核心,TS通过泛型和接口实现类型约束,而JS仅依赖运行时校验。
3.1 ref API对比(基础类型/对象类型)
| 场景 | JavaScript写法 | TypeScript写法 |
|---|---|---|
| 基础类型(数字/字符串) | `import { ref } from ‘vue’; | |
| const count = ref(0); | ||
| count.value = 10; // 直接赋值,无类型校验` | `import { ref } from ‘vue’; | |
| // 方式1:自动推导类型(Ref) | ||
| const count = ref(0); | ||
| // 方式2:显式泛型声明(推荐) | ||
| const name = ref(‘张三’); | ||
| // 方式3:联合类型 | ||
| const status = ref<‘loading’ | ‘success’ | ‘error’>(‘loading’); |
| count.value = ‘10’; // TS编译报错(类型不匹配)` | ||
| 对象类型 | `import { ref } from ‘vue’; | |
| const user = ref({ | ||
| name: ‘张三’, | ||
| age: 24 | ||
| }); | ||
| user.value.age = ‘24’; // 运行时错误,JS无法拦截` | `import { ref } from ‘vue’; | |
| // 接口约束对象结构 | ||
| interface User { | ||
| name: string; | ||
| age: number; | ||
| } | ||
| // 泛型指定类型 | ||
| const user = ref({ | ||
| name: ‘张三’, | ||
| age: 24 | ||
| }); | ||
| user.value.age = ‘24’; // TS编译报错(数字≠字符串) | ||
| user.value.gender = ‘男’; // TS编译报错(无该属性)` |
3.2 reactive API对比(复杂对象)
核心差异:reactive返回的是响应式代理对象,TS必须通过泛型指定类型,不能用类型注解(会导致类型不匹配)。
JavaScript写法
import{reactive}from'vue';// 无类型约束,属性可随意添加constform=reactive({username:'',password:''});form.remember=true;// 随意添加属性,JS不拦截TypeScript写法
import{reactive,readonly}from'vue';// 1. 接口定义表单结构interfaceLoginForm{username:string;password:string;remember?:boolean;// 可选属性}// 2. 泛型指定类型(必须用泛型,不能用类型注解)constform=reactive<LoginForm>({username:'',password:''});// 3. 只读响应式(TS+Vue专属)constreadonlyForm=readonly(form);readonlyForm.username='test';// TS编译报错(只读)// 错误写法示例constwrongForm:LoginForm=reactive({});// 报错:响应式代理无法赋值给原始类型3.3 computed API对比(计算属性)
JavaScript写法
import{ref,computed}from'vue';constcount=ref(0);// 无返回值类型约束constdoubleCount=computed(()=>{returncount.value*2;});// 可写计算属性constfullName=computed({get(){return`${firstName.value}${lastName.value}`;},set(val){const[first,last]=val.split(' ');firstName.value=first;lastName.value=last;}});TypeScript写法
import{ref,computed}from'vue';constcount=ref<number>(0);// 1. 自动推导返回类型(ComputedRef<number>)constdoubleCount=computed(()=>{returncount.value*2;});// 2. 显式声明返回类型constdoubleCount2=computed<number>(()=>{returncount.value*2;});// 3. 可写计算属性类型约束constfirstName=ref<string>('张');constlastName=ref<string>('三');constfullName=computed<string>({get():string{return`${firstName.value}${lastName.value}`;},set(val:string){const[first,last]=val.split(' ')||['',''];firstName.value=first;lastName.value=last;}});四、Props/Emits类型约束差异
Props和Emits是组件通信的核心,JS仅支持运行时校验(如type: String),而TS能实现编译时类型校验,提前规避传参错误。
4.1 Props定义对比
JavaScript写法(运行时校验)
<script setup>import{defineProps}from'vue';// 仅指定类型和默认值,无编译时校验constprops=defineProps({id:{type:Number,required:true},name:{type:String,default:'匿名用户'},tags:{type:Array,default:()=>[]}});// 使用props时无类型提示console.log(props.id,props.name);</script>TypeScript写法(编译时校验)
<script setup lang="ts">import{defineProps,withDefaults}from'vue';// 1. 接口定义Props结构(TS专属)interfaceProps{id:number;// 必传属性name?:string;// 可选属性tags:string[];// 数组类型user:{// 嵌套对象类型name:string;age:number;};}// 2. 基础类型约束(无默认值)constprops1=defineProps<Props>();// 3. 带默认值的Props(withDefaults)constprops=withDefaults(defineProps<Props>(),{name:'匿名用户',tags:()=>['default'],// 嵌套对象默认值需返回函数user:()=>({name:'默认用户',age:18})});// 使用props时有完整类型提示console.log(props.id);// 鼠标悬浮显示number类型props.id=10;// TS编译报错(props只读)</script>4.2 Emits定义对比
JavaScript写法(仅声明事件名)
<script setup>import{defineEmits}from'vue';// 仅声明事件名,无参数类型约束constemit=defineEmits(['change','submit']);// 触发事件时参数随意传递,JS不拦截consthandleSubmit=()=>{emit('submit',{username:'张三',password:123456});// 密码应为字符串};</script>TypeScript写法(事件参数类型约束)
<script setup lang="ts">import{defineEmits}from'vue';// 方式1:类型字面量(推荐)constemit=defineEmits<{// 事件名:(参数类型) => 返回值(通常为void)change:(id:number)=>void;submit:(form:{username:string;password:string})=>void;}>();// 方式2:接口约束interfaceEmits{(e:'change',id:number):void;(e:'submit',form:{username:string;password:string}):void;}constemit2=defineEmits<Emits>();// 触发事件时参数类型不匹配则报错consthandleSubmit=()=>{emit('submit',{username:'张三',password:'123456'});// 正确emit('submit',{username:'张三',password:123456});// TS编译报错};</script>五、生命周期钩子用法差异
两者生命周期钩子的调用时机完全一致,差异仅体现在TS对参数类型和DOM元素类型的约束上。
5.1 基础生命周期对比
JavaScript写法
import{onMounted,onUpdated,ref}from'vue';consttitle=ref('初始标题');onMounted(()=>{// 操作DOM无类型提示,可能出现null错误constel=document.getElementById('title');el.style.color='red';// 若el为null,运行时报错});onUpdated(()=>{console.log('组件更新',title.value);});TypeScript写法
import{onMounted,onUpdated,ref}from'vue';consttitle=ref<string>('初始标题');onMounted(()=>{// 1. 类型断言(明确DOM元素类型)constel=document.getElementById('title')asHTMLDivElement;el.style.color='red';// 有完整CSS属性提示// 2. 类型守卫(避免null错误)constel2=document.getElementById('title');if(el2){// 自动缩小类型为HTMLDivElementel2.style.fontSize='16px';}});onUpdated(()=>{console.log('组件更新',title.value);// title类型明确为string});5.2 自定义Hook中的生命周期
JavaScript写法(useTimer.js)
import{ref,onMounted,onUnmounted}from'vue';// 无参数类型和返回值类型约束exportconstuseTimer=(initialValue=0)=>{constcount=ref(initialValue);lettimer=null;onMounted(()=>{timer=setInterval(()=>{count.value++;},1000);});onUnmounted(()=>{clearInterval(timer);});return{count};};TypeScript写法(useTimer.ts)
import{ref,onMounted,onUnmounted,typeRef}from'vue';// 1. 泛型约束入参类型,支持多种数字类型exportconstuseTimer=<Textendsnumber>(initialValue:T=0asT):{count:Ref<T>}=>{constcount=ref<T>(initialValue);// 2. 显式声明定时器类型lettimer:NodeJS.Timeout|null=null;onMounted(()=>{timer=setInterval(()=>{count.value=(count.value+1)asT;},1000);});onUnmounted(()=>{if(timer)clearInterval(timer);// 类型守卫});// 3. 明确返回值类型return{count};};TS优势:通过泛型让Hook支持多类型入参,同时显式声明定时器类型,避免JS中timer隐式为any的问题。
六、Vue Router集成差异
Vue Router 4+对TS有深度支持,TS能通过类型推导实现路由参数、导航守卫的类型安全,而JS仅依赖手动校验。
6.1 路由配置对比
JavaScript写法(router/index.js)
import{createRouter,createWebHistory}from'vue-router';importHomefrom'../views/Home.vue';importUserfrom'../views/User.vue';// 无类型约束,路由参数全靠手动记忆constroutes=[{path:'/',name:'Home',component:Home},{path:'/user/:id',name:'User',component:User,props:true// 直接传递params参数}];constrouter=createRouter({history:createWebHistory(import.meta.env.BASE_URL),routes});exportdefaultrouter;TypeScript写法(router/index.ts)
import{createRouter,createWebHistory,typeRouteRecordRaw}from'vue-router';importHomefrom'../views/Home.vue';importUserfrom'../views/User.vue';// 1. 类型约束路由配置(RouteRecordRaw)constroutes:RouteRecordRaw[]=[{path:'/',name:'Home',component:Home},{path:'/user/:id',name:'User',component:User,props:true}];constrouter=createRouter({history:createWebHistory(import.meta.env.BASE_URL),routes});// 2. 路由守卫类型约束(TS专属)router.beforeEach((to,from,next)=>{// to/from均为RouteLocationNormalized类型,有完整参数提示if(to.name==='User'&&!to.params.id){next('/');}else{next();}});exportdefaultrouter;// 3. 路由参数类型声明(全局类型增强)declaremodule'vue-router'{interfaceRouteParams{id?:string;// 声明/user/:id的id参数类型}}6.2 组件内路由使用对比
JavaScript写法
<script setup>import{useRouter,useRoute}from'vue-router';constrouter=useRouter();constroute=useRoute();// 无参数类型提示,容易传错constgoUser=(id)=>{router.push({name:'User',params:{id}});};// 路由参数类型未知,需手动转换constuserId=route.params.id;// 可能为string或undefinedconstuserIdNum=Number(userId);// 手动转数字</script>TypeScript写法(带类型推导)
<script setup lang="ts">import{useRouter,useRoute}from'vue-router';importtype{RouteLocationNamedRaw}from'vue-router';// 1. 路由实例类型明确constrouter=useRouter();constroute=useRoute();// 2. 路由跳转参数类型约束constgoUser=(id:number)=>{constto:RouteLocationNamedRaw={name:'User',params:{id:id.toString()}// 明确params类型为string};router.push(to);};// 3. 路由参数类型安全获取constuserId=route.params.id;// TS自动推导为string | undefinedif(userId){constuserIdNum=Number(userId);// 类型守卫后安全转换}// 4. 路由查询参数类型约束constpage=route.query.page?Number(route.query.page):1;</script>进阶技巧:Vue Router 4.4+支持通过unplugin-vue-router插件自动生成路由类型,无需手动声明路由参数。
七、Pinia状态管理差异
Pinia作为Vue3官方状态管理库,原生支持TS,其与JS的差异体现在状态类型推导和Action/Getter类型约束上。
7.1 Store定义对比
JavaScript写法(useUserStore.js)
import{defineStore}from'pinia';// 无类型约束,状态属性可随意修改exportconstuseUserStore=defineStore('user',{state:()=>({name:'张三',age:24,roles:[]}),getters:{// 无返回值类型约束fullName(){return`用户:${this.name}`;}},actions:{// 无参数类型约束setAge(age){this.age=age;// 若传入字符串,运行时出错},// 异步Action无返回值类型asyncfetchUser(){constres=awaitfetch('/api/user');constdata=awaitres.json();this.$patch(data);}}});TypeScript写法(useUserStore.ts)
import{defineStore}from'pinia';// 1. 接口定义状态类型(TS专属)interfaceUserState{name:string;age:number;roles:string[];}// 2. 选项式Store(带类型约束)exportconstuseUserStore=defineStore('user',{// 显式声明state返回值类型state:():UserState=>({name:'张三',age:24,roles:[]}),getters:{// 3. Getter返回值类型约束fullName:(state):string=>{return`用户:${state.name}`;// state类型自动推导}},actions:{// 4. Action参数类型约束setAge(age:number):void{this.age=age;// 传入非数字则编译报错},// 5. 异步Action返回值类型(Promise)asyncfetchUser():Promise<UserState>{constres=awaitfetch('/api/user');constdata=awaitres.json()asUserState;// 类型断言this.$patch(data);returndata;}}});// 3. 组合式Store(更灵活的类型控制)exportconstuseCounterStore=defineStore('counter',()=>{constcount=ref<number>(0);// 显式类型constincrement=(step:number=1):void=>{count.value+=step;};return{count,increment};});7.2 Store使用对比
JavaScript写法
<script setup>import{useUserStore}from'@/stores/useUserStore';constuserStore=useUserStore();// 无类型提示,容易调用错误方法userStore.setAge('25');// 运行时错误console.log(userStore.fullName);// 无返回值类型提示</script>TypeScript写法
<script setup lang="ts">import{useUserStore}from'@/stores/useUserStore';constuserStore=useUserStore();// 1. 调用Action时有参数类型提示userStore.setAge(25);// 正确userStore.setAge('25');// TS编译报错// 2. 获取Getter时有明确类型constfullName:string=userStore.fullName;// 类型匹配// 3. 异步Action类型约束constfetchData=async()=>{constuserData=awaituserStore.fetchUser();// userData类型为UserState};</script>关键优势:Pinia在TS环境下会自动推导Store实例的类型,无需手动声明,且支持通过接口严格约束状态结构。
八、自定义Hook封装差异
自定义Hook是Vue3组合式API的核心复用方式,TS通过泛型和类型导出让Hook更通用、更安全。
8.1 数据请求Hook对比(useRequest)
JavaScript写法(useRequest.js)
import{ref}from'vue';// 无类型约束,返回值类型混乱exportconstuseRequest=(requestFn)=>{constdata=ref(null);constloading=ref(false);consterror=ref(null);constfetchData=async(...args)=>{loading.value=true;try{constres=awaitrequestFn(...args);data.value=res.data;returnres.data;}catch(err){error.value=err;throwerr;}finally{loading.value=false;}};return{data,loading,error,fetchData};};TypeScript写法(useRequest.ts)
import{ref,typeRef}from'vue';// 1. 泛型定义响应数据类型(支持任意接口返回格式)exportconstuseRequest=<T,Pextendsany[]=[]>(requestFn:(...args:P)=>Promise<{data:T}>)=>{// 2. 状态类型明确constdata:Ref<T|null>=ref(null);constloading:Ref<boolean>=ref(false);consterror:Ref<Error|null>=ref(null);// 3. 函数参数与返回值类型约束constfetchData=async(...args:P):Promise<T>=>{loading.value=true;try{constres=awaitrequestFn(...args);data.value=res.data;returnres.data;}catch(err){error.value=errasError;// 类型断言throwerr;}finally{loading.value=false;}};// 4. 明确返回值类型return{data,loading,error,fetchData};};// 接口定义API返回类型(TS专属)interfaceUserData{id:number;name:string;}// 使用示例:指定泛型类型为UserDataconst{data,fetchData}=useRequest<UserData,[number]>((id)=>fetch(`/api/user/${id}`).then(res=>res.json()));TS优势:通过泛型让Hook支持任意请求参数和响应类型,复用性更强,且能自动推导返回值类型。
九、异步请求与事件处理差异
异步请求和DOM事件处理是前端开发高频场景,TS通过类型约束避免回调函数参数类型错误。
9.1 异步请求对比(Axios)
JavaScript写法
importaxiosfrom'axios';// 无响应数据类型约束,需手动判断属性constfetchUser=async(id)=>{try{constres=awaitaxios.get(`/api/user/${id}`);// 无类型提示,可能访问不存在的属性console.log(res.data.user.name,res.data.user.age);returnres.data;}catch(err){console.error('请求失败',err);}};TypeScript写法
importaxiosfrom'axios';// 1. 接口定义响应数据类型interfaceUserResponse{code:number;data:{user:{name:string;age:number;};};message:string;}// 2. 泛型指定响应类型constfetchUser=async(id:number):Promise<UserResponse['data']>=>{try{// 3. Axios泛型参数指定响应类型constres=awaitaxios.get<UserResponse>(`/api/user/${id}`);// 有完整属性提示,避免访问不存在的字段console.log(res.data.user.name,res.data.user.age);returnres.data;}catch(err){// 4. 错误类型处理if(axios.isAxiosError(err)){console.error('请求失败',err.response?.data.message);}else{console.error('未知错误',err);}throwerr;}};9.2 DOM事件处理对比
JavaScript写法
<template><input type="text"@input="handleInput"/><button @click="handleClick">点击</button></template><script setup>consthandleInput=(e)=>{// 无事件对象类型提示,需手动记忆属性console.log('输入内容',e.target.value);};consthandleClick=(e)=>{// 可能误写属性名e.preventDefault();};</script>TypeScript写法
<template><inputtype="text"@input="handleInput"/><button @click="handleClick">点击</button></template><script setup lang="ts">// 1. 明确事件对象类型consthandleInput=(e:Event)=>{// 2. 类型断言为HTMLInputElementconstinputEl=e.targetasHTMLInputElement;console.log('输入内容',inputEl.value);// 有value属性提示};// 3. 鼠标事件类型consthandleClick=(e:MouseEvent)=>{e.preventDefault();// 有完整方法提示};</script>十、TS专属类型工具与语法
以下语法为TS专属,JS完全不支持,是TS类型安全的核心保障,需重点掌握。
10.1 基础类型工具
import{ref,reactive}from'vue';// 1. 接口(Interface):定义对象结构interfaceUser{name:string;age:number;gender?:string;// 可选属性readonlyid:number;// 只读属性}// 2. 类型别名(Type):定义任意类型typeStatus='loading'|'success'|'error';// 联合类型typeUserList=User[];// 数组类型typeNullable<T>=T|null;// 泛型类型别名// 3. 泛型(Generic):实现通用类型constcreateArray=<T>(length:number,value:T):T[]=>{returnArray(length).fill(value);};constnumberArray=createArray<number>(3,0);// [0,0,0]conststringArray=createArray<string>(3,'a');// ['a','a','a']// 4. 交叉类型(Intersection):合并多个类型typeAdmin=User&{role:'admin'};constadmin:Admin={id:1,name:'管理员',age:30,role:'admin'};// 5. 工具类型(Utility Types):Vue开发高频使用// Partial:将属性变为可选constupdateUser=(user:User,updates:Partial<User>)=>{return{...user,...updates};};updateUser(admin,{age:31});// 仅更新age属性// Pick:筛选属性typeUserName=Pick<User,'name'|'id'>;// 仅包含name和id// Omit:排除属性typeUserWithoutId=Omit<User,'id'>;// 排除id属性// Exclude:排除联合类型中的部分类型typeNonLoadingStatus=Exclude<Status,'loading'>;// 'success' | 'error'10.2 Vue3开发高频TS语法
类型断言:
const el = e.target as HTMLInputElement,明确变量类型;类型守卫:
if (typeof value === 'number'),缩小变量类型范围;空值合并:
const name = user.name ?? '匿名',处理null/undefined;可选链:
const age = user?.age,安全访问嵌套属性;类型导入:
import type { User } from './types',仅导入类型(不生成代码)。
十一、核心差异总结表
| 对比维度 | JavaScript写法特点 | TypeScript写法特点 |
|---|---|---|
| 类型约束 | 无显式类型,依赖运行时校验 | 编译时类型校验,支持接口、泛型 |
| 组件定义 | 简洁无类型,this隐式为any | 需lang="ts",defineComponent推导类型 |
| 响应式API | 直接使用,无类型约束 | ref/reactive需泛型,computed指定返回值类型 |
| Props/Emits | 运行时校验(type: String) | 接口约束Props,类型字面量约束Emits参数 |
| 路由集成 | 无参数类型提示,手动转换 |