Skip to content

模块管理

模块

TS支持JS的所有模块化方案,TS使用了ES Module作为默认的模块化方案。

TS文件中没有添加importexport,都会被认为是一个全局脚本而不是模块。被认为是脚本内容是全局可见的,容易造成全局变量污染、变量名冲突等问题。

类型导入

在导入类型时,明确标注导入的内容是TS类型,目的是让非TS编译器比如babelswc在编译的时候跳过语法检查,并安全移除类型。

typescript
import { type IProps } from './typings/module'
import type { IProps, IPerson } from './typings/module'

declare

declareTS中用于声明一个变量、类型、函数或模块的存在,不需要提供具体的实现,声明的变量尽量放在.d.ts文件中,做到实现和类型分离。

声明全局变量、函数、类型

typescript
// global.d.ts
declare const APP_ENV: 'development' | 'production';
declare function trackEvent(eventName: string): void;

// 使用
console.log(APP_ENV); // 类型安全检测
trackEvent('page_load'); // ✅ 正确
trackEvent(123); // ❌ 类型错误

为第三方模块声明类型

typescript
// jquery.d.ts
declare module 'jquery' {
  export function ajax(url: string): Promise<any>;
  export const version: string;
}

// 使用
import { ajax } from 'jquery';
ajax('/api/data'); // 获得类型检查

扩展已有类型

typescript
// 扩展 Window 对象
declare global {
  interface Window {
    _gaq: any[];
  }
}

// 使用
window._gaq.push(['trackPageview']); // 不再报错

声明模块(非JS资源)

typescript
// images.d.ts
declare module '*.png' {
  const src: string;
  export default src;
}

// 使用
import logo from './logo.png'; // 类型为 string

类型声明文件.d.ts

.d.ts是用来做类型声明的文件,仅仅用来做类型检查,告诉TS存在的类型,它不存在任何JS运行时代码的实现,声明的内容默认是全局可见的。

typescript
// type.d.ts
type IProps = {};

// 使用,无需导入
const props: IProps = {};

编译器在查找类型时,会根据就近原则,会在以下的文件进行查找:

  • 内置类型声明:TS自带类型库,内置了JS运行时标准API的声明文件。
  • 第三方包类型声明:第三方包编写的类型文件,一般在包的构建产物就带有,或者在/node_modules/@types/xxx目录下。
  • 自定义类型声明:有的第三方包是JS编写的可能会存在没有类型文件的情况,开发者可以自己编写类型声明文件,为第三方包提供类型。

命名空间

命名空间是早期TS用于解决全局作用域污染的方案,但现代的模块化体系已经能很好处理作用域隔离的问题,所以命名空间渐渐退出舞台。

namespace相当于一个独立的作用域,可以在里面正常编写TS代码,比如下面的代码:

typescript
namespace Utils {
  export function getRandomNum() {
    return Math.random();
  }
}

const randomNum = Utils.getRandomNum();

TS文件中使用namespace最终会被编译成JS代码,就像下面的结果:

javascript
var Utils;
(function (Utils) {
    function getRandomNum() {
        return Math.random();
    }
    Utils.getRandomNum = getRandomNum;
})(Utils || (Utils = {}));
var randomNum = Utils.getRandomNum();

在模块化的项目中,优先使用ES Module;在非模块化的全局脚本中,命名空间仍然有用。

类型合并

typescript
// 合并分散的类型定义
// user.d.ts
namespace API {
  export interface User { id: number; }
}

// product.d.ts
namespace API {
  export interface Product { sku: string; } // 合并到同一命名空间
}

全局类型扩展

搭配declare使用,形成全局类型。

typescript
// 扩展第三方库类型
declare namespace React {
  interface FunctionComponent {
    displayName?: string;
  }
}

兼容全局脚本

在兼容以CDN或者没有类型的全局脚本的场景,namespace是很有用处的。

html
<!-- 传统脚本引入 -->
<script src="jquery"></script>
ts
// jquery.d.ts
declare module "jquery" {
  export function ajax(url: string): Promise<any>;
  export const version: string;
}

三斜线指令

/// <reference> 语法是一种特殊的TS注释,用于编译时告诉编译器在编译过程中要引入额外的类型文件。

声明语句通常出现在文件顶部,如果出现在一个语句或声明之后,会被视为普通的单行注释,不会有任何特殊含义。

引入类型文件

它可以引入本地路径的类型文件(.d.ts)、@types的类型包、内置的lib类型声明(ES2015dom等)

  • 路径引入
typescript
// 相对路径
/// <reference path="./types/index.d.ts" />

// 绝对路径
/// <reference path="D:/project/types/index.d.ts" />
  • @types类型包
typescript
// @types的类型包
/// <reference types="node" />
  • 内置的lib类型
typescript
// 内置lib类型
/// <reference lib="es2015" />

编译选项

当使用 /// <reference> 时,需要确保 TypeScript 编译器知道如何处理这些指令。可以通过以下编译选项来配置:

  • --noResolve:禁用自动解析模块,编译器不会自动解析 /// <reference> 中的文件
  • --noLib:禁用所有默认标准库文件,需要手动指定 /// <reference>
typescript
// 在 TypeScript 文件顶部手动引入 ES2015 标准库
/// <reference lib="es2015" />

WARNING

它的处境跟命名空间是一样的,在现代的模块化体系项目中,优先使用模块import/export的方式导入导出。如果在没有模块系统的环境中开发,或者需要处理全局变量、全局类型时,/// <reference> 会派上用场。

最佳实践

场景推荐方案
第三方库类型扩展**declare module**
项目自有类型模块化导出 (**export**)
全局环境变量**global.d.ts**+**declare**
复杂类型组织命名空间嵌套
类型复用集中导出 (**index.ts**)
非 JS 资源导入模块声明 (***.d.ts**)
最佳实践
  • 扩展第三方包
ts
declare module "vue" {
  interface ComponentCustomProperties {
    $translate: (key: string) => string;
  }
}

// 如果没有export 将会覆盖vue的全部类型声明,而不是扩展它
export {}
  • 扩展文件资源模块
ts
/// <reference types="vite/client" />

declare module "*.vue" {
  import type { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
  • 扩展全局属性
ts
declare global {
  interface Window {
    _gaq: any[];
  }
}
  • 请求:整合类型

对于网络请求的请求体DTO和响应体Resp要抽离成类型声明文件,建议整合类型,并且声明命名空间API

ts
// camera.d.ts
declare namespace API{
  interface CameraDto{}
}

// base-device.d.ts
declare namespace API{
  interface BaseDeviceResp{}
}

如有转载或 CV 的请标注本站原文地址