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
}
}
八、其他需要处理的点
-
环境变量
.env
import.meta.envTS 会报类型不存在,在env.d.ts声明///
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