Typescript全局类型声明
现阶段的前端项目越来越少不了第三方库的使用,而第三方库不一定都是用ts编写的、也不一定都遵守了统一规范,那么这种情况下在ts项目中就会出现找不到变量相关类型声明的情况,这时候就需要在项目中进行全局类型声明了
又或者在大型项目中需要在全局定义一些全局变量、给全局对象补充自定义属性方法等等,这些都需要用到全局类型声明
本文就来看看全局类型声明及相关一些技巧吧
小贴士
文章中涉及到的代码示例你都可以从 这里查看 ,若对你有用还望点赞支持;你可以在 TS Playground 尝试开始Typescript的学习
前置知识
类型声明需要在.d.ts
或.ts
结尾的文件中定义,且需要在tsconfig.json
中包含类型定义文件
{
"include": [/* 类型定义文件 */]
}
2
3
ts中的模块有全局模块(ambient module)
和文件模块
之分,什么意思呢❓
假设有个a.ts
:
type ICard = string;
interface IUser {};
2
此时a.ts
就是全局模块,里面声明的类型,在其他文件不引入也可以正常使用
再看看b.d.ts
文件:
type ICard = string;
export interface IUser {};
2
此时b.d.ts
就是个文件模块,里面的类型变量就不再是全局的了,必须要使用import
引入才可以使用。那么IUser
可以使用,ICard
没有导出是没法使用的
如果一个文件中包含了import/export
引入第三方模块了,里面的变量将不再是全局变量
那全部使用第一种方式很有可能造成变量污染,使类型声明不可控,通常我们都是遵循:项目级别和第三方模块类型可声明全局,而普通的变量类型以文件模块声明,使用时以导入导出的形式加载
扩展window对象
工作中被同事问到扩展window对象属性的问题最多,其实很简单
interface Window {
__DEV__: boolean;
__DATE__: string;
}
2
3
4
或者经常看到有人这样用:
declare global {
interface window{
__DEV__: boolean;
__DATE__: string;
}
}
export {};
2
3
4
5
6
7
8
第二种其实多此一举,这里global被看做其他模块了,所以要使用export {}
,不然会报错
declare
上面我们看到了declare
关键字,这个关键字用来表示已经真实存在的变量、类型、函数、模块等等
举几个例子:
declare const __DEV__: boolean; // 全局变量
declare function logger(msg: string): void; // 全局函数
2
命名空间namespace
namespace 类似于 JavaScript 中的作用域。通过 namespace 开发者可以将相关的类型、函数、变量等元素封装在一个逻辑容器内,从而避免全局命名空间污染,并提高代码的可维护性和可读性
使用namespace很简单,用固定格式定义模块,然后内部使用export
暴露变量就可以,基本上和模块导出类似,而没有导出的变量是没法使用的
// 定义Color命名空间
declare namespace Color {
export const red: string;
export const green: string;
export const blue: string;
}
// 使用
let color = Color.blue;
2
3
4
5
6
7
8
9
注意上面的Color.blue
的Color不是namespace Color
的那个Color,一定要知道declare只是声明存在,并不代表真实的对象
或者你也可以不用declare
关键字,那么定义的就是真实值
// 这里就是真实对象,必须初始化值
namespace Colors {
export const red: string = 'RED';
export const green: string = 'green';
export const blue: string = 'blue';
}
const color = Colors.red;
2
3
4
5
6
7
8
模块module
declare module
是一种用于声明模块接口的方式,主要用于告诉TypeScript编译器某个模块的存在及其包含的内容。这种声明方式特别适用于处理外部模块或为现有的JavaScript代码添加TypeScript类型信息
declare module 'module-name' {
// 模块的成员定义
}
2
3
比如现在使用比较老版本的jQuery,没有任何类型信息,这时候就可以使用declare module
来声明jQuery中的成员变量
declare module 'jquery' {
const $: (selector: string) => any;
// ...
}
// 使用jquery
import jquery from 'jquery';
jquery.$(''); // 这里就有类型提示了
2
3
4
5
6
7
8
module和namespace的区别❓
- module主要用来在全局声明第三方模块中的成员变量类型信息,来弥补第三方库类型缺陷
- namespace是ts提供的代码分割代码作用域的一种手段,既可以用来声明作用域类型,也可以生成作用域真实的变量成员
在ts中推荐使用module来声明模块类型‼️
当然也可以扩展第三方模块的类型:
import "vue";
declare module "vue" {
export interface AppConfig {
readonly __APP_VERSION__: string;
}
const launch: () => void;
}
// 使用
import { createApp, launch } from "vue";
const app = createApp({});
app.config.__APP_VERSION__;
launch();
2
3
4
5
6
7
8
9
10
11
12
13
小贴士
扩展类型一定要遵循可扩展规范,如类型一定要使用interface
定义才可以扩展,而使用type
是没办法扩展的
三斜线指令
三斜线指令(Triple-Slash Directives)是一种用于向编译器提供额外信息的特殊注释。这些指令可以帮助开发者更好地管理模块和类型定义,特别是在处理大型项目或混合型代码库时非常有用
最简单的形式是:
/// <reference path="file.ts" />
这行代码告诉TypeScript编译器在编译当前文件时,也包含指定的file.ts
中的内容。这意味着如果file.ts
中有一些共享的类型定义或接口,使用这个指令可以让所有引用它的文件都能访问到这些类型
但是在前面前置知识
那里,我们讲了项目中定义的类型如果没有export/import
都会被视为全局变量,在项目中的任何地方都可以访问到,那还是用三斜线的意义在哪里❓
需要注意的是如果使用到的类型没有被包含在tsconfig
中的include
中,就无法识别到。那你可能会说,可以使用:
import { someType } from 'some-module';
来显示引入它来解决问题。但这种方式并没法使用模块中没有导出的变量
// 可使用,因为导出了
export interface IUser {
name: string;
}
// 没有导出,不可以使用
type person = boolean;
2
3
4
5
6
这时候三斜线指令就发挥作用了:
/// <reference path="../some-module" />
这样就可以使用person
了,但是你会发现IUser
又没法使用了,所以到这里你应该明白:
三斜线指令主要用来引入不想暴露出去的类型声明文件,尤其是那些第三方库文件,没有暴露出去的类型,可以使用此种方式访问到
常见的几种三斜线指令
/// <reference path="文件路径" />
/// <reference types="模块" />
/// <reference lib="内置模块" />
2
3
path
:主要引入某个具体的文件types
:主要引入某个库或者包的类型,如node、vue等等lib
:主要引入ts标准内置库中的某个模块类型,如dom等等
发布有类型声明的库
现在你有一个使用ts开发的工具库已经通过测试后想发布到公网,提供自己日常工作使用,需要注意点是什么呢,或者如何在项目中正确识别到第三方库类型呢?
首先打包自己的工具包,一定要注意ts要开启类型声明配置:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"outDir": "dist",
// 开启类型声明
"declaration": true,
// 类型输出目录
"declarationDir": "dist/types"
},
"include": ["./src/**/*"]
}
2
3
4
5
6
7
8
9
10
11
12
13
然后需要在package.json
中定义类型路径:
{
"types": "./dist/types/main.d.ts"
}
2
3
这样在项目中引入工具库时,就会根据查找原则找到types
,就可以正确显示类型了,当然需要将暴露给外部的类型通过export
导出,如果没有可以使用三斜线指令
除此之外,如果工具包产物中没有类型声明时,也可以专门定义一个@types/工具包的库,然后项目中会自动查找@types
下的类型声明文件。官方提供了types-publisher tool 可以参考
开发技巧
- 项目中自定义的全局变量声明在一个文件中
- 对于第三方模块文件扩展放在一个文件
- 项目中的普通类型使用模块类型导入导出使用
总结
到这里基本上已足以在工作中胜任Typescript了,那些深度类型体操相关不是很重要,通常也不会用到。后面就是装饰器的高级知识了,它不仅是一个语法,也是一种设计模式,搞明白它也比较重要