JS 项目转 TS 项目(Vue3 + 若依这类后台项目)完整改造清单
我们当前项目:Vue3 + Vite + Pinia + ElementPlus,JS → TS,分环境安装、配置文件、代码改造、类型定义、逐步迁移5 大块,不一次性全改,支持渐进式迁移。
一、第一步:安装 TS 相关依赖
# pnpm pnpm add -D typescript @vitejs/plugin-vue vue-tsc @types/node包说明:
typescript:ts 核心编译器vue-tsc:专门校验 Vue 单文件组件<script setup>类型@types/node:node 内置 API 类型(import.meta、path 等)@vitejs/plugin-vue:vite 处理 vue,TS 版本也需要
二、修改 Vite 配置vite.config.js→vite.config.ts
- 文件名改后缀:
.js→.ts - 顶部导入改成 TS 写法
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': resolve(__dirname, 'src') } } })注意:
__dirname在 TS 里需要@types/node,否则飘红
三、新增 TS 配置文件tsconfig.json(核心)
{ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], "skipLibCheck": true, /* 模块解析 */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, /* vue类型 */ "jsx": "preserve", /* 类型严格度:开发推荐严格模式,不想一下子大量报错可以先false */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] }同时新建tsconfig.node.json给 vite 配置文件用
{ "compilerOptions": { "composite": true, "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts"] }四、package.json 修改脚本命令
"scripts": { "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview" }build 前面加
vue-tsc:打包前先做类型校验,类型错误直接阻止打包。
五、Vue 单文件组件改造(重点,我们的 tags-view、驾驶舱页面都要改)
1. script 标签替换
JS 版本:
<script setup>TS 版本:
<script setup lang="ts">✅ 只加
lang="ts",文件后缀依旧是.vue,不用改成.ts
2. 变量、接口、类型定义
JS:
const statData = { userTotal: 0, newsTotal: 0 }TS:定义接口约束数据结构
interface StatData { userTotal: number newsTotal: number deptTotal: number } const statData: StatData = reactive<StatData>({ userTotal: 0, newsTotal: 0, deptTotal: 0 })3. Pinia store 改造(我们的 userStore、tagsViewStore)
原stores/user.js→stores/user.ts
import { defineStore } from 'pinia' interface UserState { token: string name: string } export const useUserStore = defineStore('user', { state: (): UserState => ({ token: '', name: '' }), actions: { setToken(val: string) { this.token = val } } })六、全局类型声明src/types/index.d.ts(非常关键)
新建src/types/index.d.ts,放全局类型,解决 vue 文件、第三方模块找不到类型报错
// 识别 *.vue 文件 declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component } // 标签页对象类型(直接复用我们tags-view的tag类型) export interface TagView { path: string title: string name?: string }以后所有页面都可以直接
import type { TagView }使用
七、路由文件改造 router/index.js → router/index.ts
路由里的路由项,我们可以用 TS 内置类型RouteRecordRaw
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' // 路由数组加上类型 export const constantRoutes: RouteRecordRaw[] = [ { path: '/cockpit', name: 'Cockpit', component: () => import('@/views/cockpit/cockpit.vue'), meta: { title: '驾驶舱大屏' } } ]路由 meta 自定义字段需要扩展类型,否则 meta 飘红:src/types/router.d.ts
import 'vue-router' declare module 'vue-router' { interface RouteMeta { title?: string } }八、其他需要处理的点
- 环境变量
.envimport.meta.envTS 会报类型不存在,在env.d.ts声明
/// <reference types="vite/client" /> interface ImportMetaEnv { readonly VITE_BASE_URL: string } interface ImportMeta { readonly env: ImportMetaEnv }- axios 请求封装
request.js → request.ts,定义请求返回泛型,统一接口返回格式。 - 第三方缺少类型包
有些老插件没有 TS 类型:declare module 'xxx'在.d.ts声明,关闭类型检查。 - 静态资源:图片 / 字体
.d.ts添加:
declare module '*.png' declare module '*.jpg'九、推荐迁移策略(不要一次性全部改完!渐进式)
- 先搭好 tsconfig、依赖、vite 配置;此时项目依旧可以跑 JS 文件(TS 支持混写 JS+TS)
- 优先改核心文件:路由、pinia store
- 再改公共组件:tags-view(就是我们刚写右键菜单这个组件)
- 最后改业务页面:驾驶舱大屏等页面
- 全部迁移完成后,tsconfig
strict:true开启严格校验
十、常见坑提前说
@别名路径飘红:tsconfig.jsonpaths没配置好.vue文件报找不到模块:缺少declare module '*.vue'__dirname报错:缺少@types/node- 打包失败:忘记在 package.json build 脚本添加
vue-tsc