文章

使用 types / typings 实现全局 TypeScript 类型定义,无需 import/export

使用 types / typings 实现全局 TypeScript 类型定义,无需 import/export

在 Vue 3 + Vite + TypeScript 项目中,我们经常需要在多个文件中使用相同的类型定义。通常的做法是:

1
2
3
4
5
// user.ts
export interface User {
  id: number;
  name: string;
}

然后在其他文件中使用时导入:

1
import type { User } from '@/types/user';

这没有问题,但在一个大型项目中,频繁 import/export 类型会显得繁琐、重复。而 TypeScript 实际上支持「全局类型声明」,允许你在项目中任何地方使用类型而不需要显式导入。

下面分析如何使用 typestypings 文件夹,在 Vue 3 + Vite + TypeScript 项目中配置全局类型定义。

1. 创建全局类型定义目录

你可以在项目根目录下创建一个 typestypings 文件夹:

1
2
3
4
5
6
7
8
your-project/
├─ src/
├─ types/          <-- 这里放全局类型定义
│  ├─ global.d.ts  <-- 推荐用 .d.ts 扩展名
│  ├─ user.d.ts
│  └─ api.d.ts
├─ vite.config.ts
├─ tsconfig.json

示例:types/user.d.ts

1
2
3
4
5
6
7
// types/user.d.ts

interface User {
  id: number;
  name: string;
  email?: string;
}

⚠️ 注意:不要使用 export 或 import,TypeScript 会自动将这类 .d.ts 文件中的类型提升为全局。

2. 配置 tsconfig.json

为了让 TypeScript 自动识别 types 目录中的类型定义,你需要在 tsconfig.json 中添加 typeRoots 或直接包含它:

1
2
3
4
5
6
7
8
9
10
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "typeRoots": ["./types", "./node_modules/@types"]
  },
  "include": ["src", "types"]
}

解释:

  • typeRoots: 告诉 TypeScript 在 types 目录下查找类型定义。- include: 包含 types 文件夹,确保类型文件被编译器扫描。 注意: typeRoots如果没有配置,会隐式的指定”./node_modules/@types”,但这里我们显式的指定了,就必须显式的指定一次"./node_modules/@types"

3. 全局使用类型定义

完成以上配置后,你可以在任意 TS/JS/Vue 文件中直接使用类型:

1
2
3
4
const user: User = {
  id: 1,
  name: 'Alice'
};

不需要再 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 后,需重启 VSCode或重启ts服务 才生效 || 不适合共享 npm 包 | 如果你想封装为库分发,请用 export 模块化方式 |

使用 declare module 声明模块类型

有时候,我们会使用一些 非 TypeScript 编写的模块 或自定义的虚拟模块(如 Vite 插件注入的模块),这些模块没有类型定义。如果我们还想在不引入第三方 .d.ts 的前提下为它们添加类型,可以用 declare module 来手动声明。

为自定义模块添加类型

假设你在 Vite 中使用了一个虚拟模块:virtual:config,但这个模块没有类型提示。

你可以在 types/modules.d.ts 中添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
// types/modules.d.ts

declare module 'virtual:config' {
  interface AppConfig {
    baseUrl: string;
    enableMock: boolean;
  }

  const config: AppConfig;
  export default config;
}

这样你就可以在项目中直接这样使用:

1
2
3
import config from 'virtual:config';

console.log(config.baseUrl);

增强已有模块的类型(模块补充)

有时我们需要为已有模块(如 axios)增强类型,比如为默认实例添加一些自定义字段或方法。

1
2
3
4
5
6
7
8
9
// types/axios.d.ts

import 'axios';

declare module 'axios' {
  export interface AxiosRequestConfig {
    authToken?: string; // 自定义字段
  }
}

这个用法叫做“模块补充”,它不会创建新的模块,而是向现有模块补充类型定义,常用于增强第三方库的接口。

为非 TS 文件声明模块

如果你导入了 .svg.png.json 这类资源文件,TypeScript 默认是无法识别这些文件的类型的,需要我们手动声明,但脚手架一般都已经添加了支持,除非你在使用刀耕火种的方式自己搭建脚手架,才需要手动自己去配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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;
}

这样就可以正常导入图片或 JSON 文件:

1
2
import logo from './assets/logo.svg';
import config from './config.json';

补充:declare global 用于增强全局类型

有时候还可能需要往全局作用域中注入变量或接口,例如:

1
2
3
4
5
6
7
// types/global.d.ts
declare global {
  interface Window {
    $t: typeof import('vue-i18n').useI18n;
    appVersion: string;
  }
}

这种写法用于补充全局变量(例如 window.appVersion),或者为 Node.js 的全局环境(如 process.env)添加类型支持。

vite的ts智能提示

vite在使用env的时候,可以参考vite的文档cn.vite.dev/guide/env-a… ,它单独提供了一个vite-env.d.ts用来处理这种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface ViteTypeOptions {
  // 添加这行代码,你就可以将 ImportMetaEnv 的类型设为严格模式,
  // 这样就不允许有未知的键值了。
  // strictImportMetaEnv: unknown
}

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string;
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

小结:declare 的三种常见用法

| 用法 | 场景 || — | — || declare module | 声明外部模块、虚拟模块、资源模块等 || 模块补充(merge) | 向已存在模块(如 axios)追加字段/方法 || declare global | 向全局作用域注入变量、接口、类型定义 |

建议的结构组织方式

可以这样组织你的 types/ 目录:

1
2
3
4
5
6
7
types/
├─ global.d.ts          # 全局类型接口/变量声明
├─ user.d.ts            # 业务类型拆分模块
├─ env.d.ts             # 环境变量声明
├─ modules.d.ts         # 自定义 module 声明
├─ assets.d.ts          # 资源文件模块(如 .svg/.png)
├─ axios.d.ts           # 补充第三方库模块定义

所有文件都应以 .d.ts 结尾,以确保 TypeScript 把它们当作声明文件而不是普通模块处理。

为什么要使用 declare 关键字?

  • 无需导入第三方 .d.ts 文件时,自定义模块声明。- 向已存在模块扩展功能时(模块合并)。- 为资源文件提供类型提示。- 向 window 或全局对象注入类型。- 在无需导入的前提下,让项目的类型系统更加完整、可靠。

    总结

通过使用全局类型定义,可以:

  • 减少冗余的 import/export 操作- 简化类型引用,提高开发效率- 按模块拆分维护更清晰 只需:
  1. 在项目根目录添加 types/ 文件夹,写入 .d.ts 类型声明2. 配置 tsconfig.json 的 typeRoots 和 include3. 直接使用类型,无需导入! 只要是typescript项目就可以按这种方法使用。

本文仓库托管在github.com/cbtpro/vite…

本文由作者按照 CC BY 4.0 进行授权