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

25 阅读5分钟

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

// user.ts
export interface User {
  id: number;
  name: string;
}

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

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

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

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


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

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

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

示例:types/user.d.ts

// types/user.d.ts

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

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


2. 配置 tsconfig.json

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

{
  "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 文件中直接使用类型:

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.tsapi.d.tsenv.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 中添加如下内容:

// types/modules.d.ts

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

  const config: AppConfig;
  export default config;
}

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

import config from 'virtual:config';

console.log(config.baseUrl);

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

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

// types/axios.d.ts

import 'axios';

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

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


为非 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;
}

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

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

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

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

// 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用来处理这种情况


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/ 目录:

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.jsontypeRootsinclude
  3. 直接使用类型,无需导入!

只要是typescript项目就可以按这种方法使用。

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