解锁 TypeScript 的元编程魔法
在 Vue 3 + Vite + TypeScript 项目中,我们经常需要在多个文件中使用相同的类型定义。通常的做法是:
// user.ts
export interface User {
id: number;
name: string;
}2
3
4
5
然后在其他文件中使用时导入:
import type { User } from '@/types/user';这没有问题,但在一个大型项目中,频繁 import/export 类型会显得繁琐、重复。而 TypeScript 实际上支持「全局类型声明」,允许你在项目中任何地方使用类型而不需要显式导入。
下面分析如何使用 types 或 typings 文件夹,在 Vue 3 + Vite + TypeScript 项目中配置全局类型定义。
创建全局类型定义目录
你可以在项目根目录下创建一个 types 或 typings 文件夹:
your-project/
├─ src/
├─ types/ <-- 这里放全局类型定义
│ ├─ global.d.ts <-- 推荐用 .d.ts 扩展名
│ ├─ user.d.ts
│ └─ api.d.ts
├─ vite.config.ts
├─ tsconfig.json2
3
4
5
6
7
8
示例:types/user.d.ts
// types/user.d.ts
interface User {
id: number;
name: string;
email?: string;
}2
3
4
5
6
7
⚠️ 注意:不要使用
export或import,TypeScript 会自动将这类.d.ts文件中的类型提升为全局。
配置 tsconfig.json
为了让 TypeScript 自动识别 types 目录中的类型定义,你需要在 tsconfig.json 中添加 typeRoots 或直接包含它:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"typeRoots": ["./types", "./node_modules/@types"]
},
"include": ["src", "types"]
}2
3
4
5
6
7
8
9
10
解释:
typeRoots: 告诉 TypeScript 在types目录下查找类型定义。include: 包含types文件夹,确保类型文件被编译器扫描。
注意: typeRoots如果没有配置,会隐式的指定
"./node_modules/@types",但这里我们显式的指定了,就必须显式的指定一次"./node_modules/@types"
全局使用类型定义
完成以上配置后,你可以在任意 TS/JS/Vue 文件中直接使用类型:
const user: User = {
id: 1,
name: 'Alice'
};2
3
4
不需要再 import { User } from '@/types/user' 了。
与第三方类型文件区别
你可能注意到 node_modules/@types 中也有很多 .d.ts 文件(比如 @types/lodash)。这是 TypeScript 默认的全局类型路径之一。
通过上面的配置,我们也把 types/ 当作了一个“自定义的全局类型包”,原理是一样的。
命名建议
建议将全局类型文件命名为:
global.d.ts:统一存放项目中杂项的全局类型定义xxx.d.ts:按功能模块拆分,如user.d.ts、api.d.ts、env.d.ts
⚠注意事项
| 项 | 注意 |
|---|---|
不要使用 export/import | 否则类型就不再是全局,会失效 |
文件名需以 .d.ts 结尾 | .ts 默认不当作类型声明文件处理 |
| 编辑器需重启 | 修改 tsconfig.json 后,需重启 VS Code或重启ts服务 才生效 |
| 不适合共享 npm 包 | 如果你想封装为库分发,请用 export 模块化方式 |
使用 declare module 声明模块类型
有时候,我们会使用一些 非 TypeScript 编写的模块 或自定义的虚拟模块(如 Vite 插件注入的模块),这些模块没有类型定义。如果我们还想在不引入第三方 .d.ts 的前提下为它们添加类型,可以用 declare module 来手动声明。
为自定义模块添加类型
假设你在 Vite 中使用了一个虚拟模块:virtual:config,但这个模块没有类型提示。
你可以在 types/modules.d.ts 中添加如下内容:
// types/modules.d.ts
declare module 'virtual:config' {
interface AppConfig {
baseUrl: string;
enableMock: boolean;
}
const config: AppConfig;
export default config;
}2
3
4
5
6
7
8
9
10
11
这样你就可以在项目中直接这样使用:
import config from 'virtual:config';
console.log(config.baseUrl);2
3
增强已有模块的类型(模块补充)
有时我们需要为已有模块(如 axios)增强类型,比如为默认实例添加一些自定义字段或方法。
// types/axios.d.ts
import 'axios';
declare module 'axios' {
export interface AxiosRequestConfig {
authToken?: string; // 自定义字段
}
}2
3
4
5
6
7
8
9
这个用法叫做“模块补充”,它不会创建新的模块,而是 向现有模块补充类型定义,常用于增强第三方库的接口。
为非 TS 文件声明模块
如果你导入了 .svg、.png、.json 这类资源文件,TypeScript 默认是无法识别这些文件的类型的,需要我们手动声明,但脚手架一般都已经添加了支持,除非你在使用刀耕火种的方式自己搭建脚手架,才需要手动自己去配置:
// types/assets.d.ts
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.json' {
const value: any;
export default value;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这样就可以正常导入图片或 JSON 文件:
import logo from './assets/logo.svg';
import config from './config.json';2
补充:declare global 用于增强全局类型
有时候还可能需要往全局作用域中注入变量或接口,例如:
// types/global.d.ts
declare global {
interface Window {
$t: typeof import('vue-i18n').useI18n;
appVersion: string;
}
}2
3
4
5
6
7
这种写法用于补充全局变量(例如 window.appVersion),或者为 Node.js 的全局环境(如 process.env)添加类型支持。
vite的ts智能提示
vite 在使用 env 的时候,它单独提供了一个 vite-env.d.ts 用来处理这种情况
interface ViteTypeOptions {
// 添加这行代码,你就可以将 ImportMetaEnv 的类型设为严格模式,
// 这样就不允许有未知的键值了。
// strictImportMetaEnv: unknown
}
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string;
// 更多环境变量...
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}2
3
4
5
6
7
8
9
10
11
12
13
14
小结:declare 的三种常见用法
| 用法 | 场景 |
|---|---|
declare module | 声明外部模块、虚拟模块、资源模块等 |
模块补充(merge) | 向已存在模块(如 axios)追加字段/方法 |
declare global | 向全局作用域注入变量、接口、类型定义 |
建议的结构组织方式
可以这样组织你的 types/ 目录:
types/
├─ global.d.ts # 全局类型接口/变量声明
├─ user.d.ts # 业务类型拆分模块
├─ env.d.ts # 环境变量声明
├─ modules.d.ts # 自定义 module 声明
├─ assets.d.ts # 资源文件模块(如 .svg/.png)
├─ axios.d.ts # 补充第三方库模块定义2
3
4
5
6
7
所有文件都应以 .d.ts 结尾,以确保 TypeScript 把它们当作 声明文件 而不是普通模块处理。
为什么要使用 declare 关键字?
- 无需导入第三方
.d.ts文件时,自定义模块声明。 - 向已存在模块扩展功能时(模块合并)。
- 为资源文件提供类型提示。
- 向
window或全局对象注入类型。 - 在无需导入的前提下,让项目的类型系统更加完整、可靠。
总结
通过使用全局类型定义,可以:
- 减少冗余的
import/export操作 - 简化类型引用,提高开发效率
- 按模块拆分维护更清晰
只需:
- 在项目根目录添加
types/文件夹,写入.d.ts类型声明 - 配置
tsconfig.json的typeRoots和include - 直接使用类型,无需导入!
只要是typescript项目就可以按这种方法使用。
