ts:什么是声明文件(declare,全局声明和模块声明

1,160 阅读5分钟

ts中的declare是用于声明变量的,用来声明后面的全局变量的类型。

那么什么是声明文件呢?都知道ts和js的一大不同点在于ts需要类型声明,声明文件实际上就是给js代码补充类型标注,防止ts变异环境下不会提示js文件“缺少类型”。

声明文件

结尾扩展名:声明文件也是文件,文件名具有规范要求。以.d.ts结尾。

位置:虽然一般来说任意路径/文件名均可被ts编译器识别,为了规避实际开发中产生一些问题,推荐放置在根目录下。

.d.ts与ts文件区别:

  1. 语句开头必须为 import,export或者declare。
  2. 当使用 import 和 export 关键字时,会将文件视为模块,并按照模块系统的方式来管理依赖和导出,需要使用时需要import引入。
  3. .d.ts 文件中的声明内容可以被 TypeScript 编译器识别,从而在其他 TypeScript 或 JavaScript 文件中使用,无需显式地进行导入。

声明文件如何产生?

一些第三方插件,会有对应的声明文件。比如jquery可以直接通过npm i @types/jquery下载其声明文件。

如果@types/下找不到声明文件,就需要手写了。

如何书写声明文件?

全局声明

直接通过declare标注全局变量的类型。

// global.d.ts
declare var num: number;
declare let str: string;
declare const obj: object;
declare function fun(s: string): number;
declare enum dir {
   a,
   b,
   c,
   d
}

因为是全局声明,所以可以在任意文件中操作变量。

num = 321
str = '文字'
let o1 = obj;
fun('123').toFixed();
dir.d.toFixed();

// 报错
n = '...'
s = 0f0

允许创建多个具有不同实现的同名函数,即支持函数重载。

declare function fun(params: string): void;
declare function fun(params: number): number;

declare namespace

namespace的意思是后面的全局变量是对象:

declare namespace API {
  interface PageInfo {
    current?: number;
    pageSize?: number;
    total?: number;
    list?: Array<Record<string, any>>;
  }
}

例如: 使用其中的pageSize变量类型:

// 声明一个变量,并指定其类型为 API.PageInfo 中的 pageSize 属性的类型
let pageSize: API.PageInfo['pageSize'];

// 例如,给 pageSize 赋值
pageSize = 10; // 此时 pageSize 只能是 number 类型

// 如果尝试给 pageSize 赋值其他类型,会导致 TypeScript 报错
// pageSize = 'string'; // 错误:不能将类型“string”分配给类型“number | undefined”

使用PageInfo接口举例:

// 创建一个 PageInfo 对象
let pageInfo: API.PageInfo = {
  current: 1,
  pageSize: 20,
  total: 100,
  list: [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }],
};

// 访问 pageSize 属性
console.log(pageInfo.pageSize); // 输出 20

// 修改 pageSize 属性
pageInfo.pageSize = 30;
console.log(pageInfo.pageSize); // 输出 30
注:在.d.ts中声明的namespace不需要export,默认都会export出来,可以直接使用。

修改全局变量的模块的第三方库的声明

要修改或拓展全局的类型和方法,通过declare global

declare global {
    interface String {
        hump(input: string): string;
    }
}
export {}
// 注意: 需要export{}字样,不然会报错。
// 报错内容为:会报【全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中】
// export{}的作用实际上是让声明文件变成模块声明文件,而不是全局声明文件。

// ./src/test.ts
const str = "jye";
str.hump('abc');

模块声明

  • 全局声明文件(没有 export 和 import): 如果一个声明文件中没有使用 export 或 import 关键字,它被引入后将被视为全局声明文件。这意味着文件中的所有声明都会被视为全局可用,无需导入即可在整个项目中使用。

  • 模块声明文件(有 export 或 import): 如果一个声明文件使用了 export 或 import 关键字,它被引入后将被视为模块声明文件。在这种情况下,只有使用 export 显式导出的内容会被引入到全局命名空间中,而未被导出的内容则被视为局部声明,只能在模块内部使用。

假设有一个 .d.ts 文件 my-module.d.ts,其中定义了一个接口和一个全局函数:

// my-module.d.ts

interface MyInterface {
    name: string;
}

declare function globalFunction(): void;

全局声明文件示例: 如果 my-module.d.ts 中没有使用 export 和 import:

// app.ts

// 引入 my-module.d.ts 文件(///三斜线指令,下述解释)
/// <reference path="./my-module.d.ts" />

// 可以直接在全局范围内使用声明
let obj: MyInterface = { name: 'John' };
globalFunction(); // 可以直接调用全局函数

在这种情况下,MyInterface 和 globalFunction 都会被视为全局声明,无需显式导入即可在 app.ts 中使用。

注:三斜线指令,也是最初用来表示模块之间依赖关系。目前使用比较少,不过声明文件中,还是有很多会去使用。 常用的两种语法:

/// <reference path="./my-module.d.ts" />:表示对一个文件的依赖。
/// <reference types="type" />:表示对一个库的依赖。

三斜线的path & types,和es6的import语义相似,同时三斜线指令必须放在文件的最顶端。

模块声明文件示例: 如果 my-module.d.ts 中使用了 export:

// my-module.d.ts

export interface MyInterface {
    name: string;
}

export declare function globalFunction(): void;
// app.ts

// 引入 my-module.d.ts 文件
import { MyInterface, globalFunction } from './my-module';

// 只能使用通过 export 导出的内容
let obj: MyInterface = { name: 'John' };
globalFunction(); // 可以直接调用全局函数

在这种情况下,MyInterface 和 globalFunction 被导出并且需要使用 import 导入才能在 app.ts 中使用。未被导出的内容不会被引入到全局命名空间中,仅作为模块内部的局部声明。

参考资料:
typescript 全局变量声明文件和模块声明文件那些事儿
TypeScript系列🔥尾声篇, 什么是声明文件(declare)? [🦕全局声明篇]
TypeScript声明文件详解
ts中的declare, .d.ts, namespace的基本用法和理解