原文链接: 《TS学习笔记 - 类型声明》
类型声明有什么用呢?
我们用 js 开发的工具、库等,可以通过声明文件来利用 TS 的类型系统,实现与 TS 一样的代码提示。
一个简单的例子
有时我们可能会通过 CDN 来引入第三方的库,此时不会有任何代码提示
- 接下来我们创建一个声明文件
demo.d.ts
declare const $: (selector: string) => {
click(): void;
};
以上表示我们声明了 $ 方法,接收一个选择器字符串,返回带有 click 方法的对象
- 测试一下
常见的类型声明语句
// 声明一些全局变量
declare var $: (selector: string) => { click: void };
declare let myAge: number;
declare const isFriday: boolean;
declare function getName: string;
declare class Animal {
name: string;
}
// 声明一些全局类型
interface Person {
name: string;
}
type Student = {
name: string;
} | 'string'
需要注意的是类型声明仅在开发时使用,在其编译为 JS 代码时,类型声明的内容都会被删除
命名空间
前面简单介绍了一下如何声明全局变量类型,以一个第三方库为例做了一点调试,但仅仅只是学习了普通的变量和函数的类型声明。
假如说一个全局变量是一个对象,包含了很多的子属性,上面的内容可能就无法满足我们的需求了,这时候就需要用到命名空间。
namespace 是 TS 早期为了解决模块化而创建的关键字,中文名称为命名空间,由于历史遗留的原因,在早期 ES6 尚未出现时,TS 就提供了一种模块化方案,使用的是 module 关键字,用来表示内部模块,但由于后来 ES6 也使用了 module ,所以 TS 为了兼容 ES6,就使用了 namespace ,TS 模块更名为命名空间,后来随着 ES6 的广泛应用,现在已经不建议使用 namespace 了,推荐使用 ES6 的模块化方案 module ,所以 namespace 的应用场景就越来越少,目前大多在声明文件中使用。
它用来表示全局变量是一个对象,包含很多子属性,比如说下面的代码, jQuery 是一个全局变量,提供了子属性 ajax 方法
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
}
jQuery.ajax('/xxx');
namespace 中声明子属性不需要再加 declare,可以直接使用 var、let、const、function 等,示例如下
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
const version: number;
class Event {
blur(eventType: EventType): void
}
enum EventType {
CustomClick
}
}
jQuery.ajax('xxx');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick)
-
如果对象结构复杂,我们也可以使用
namespace嵌套的方式来声明对象,如jQuery有一个fn属性,fn也是一个对象,包含了extend方法,这是后就可以使用namespace嵌套的方式来声明对象。示例如下declare namespace jQuery { function ajax(url: string, settings?: any): void; namespace fn { function extend(object: any): void; } }
jQuery.ajax('xxx'); jQuery.fn.extend({ check: function () { return this.each(function () { this.checked = true; }) } })
上面讲过 namespace 中申明变量不需要加 declare 这里的 fn 也同样不需要加
-
如果
jQuery下面只有一个属性也可以使用下面的写法,不使用namespace嵌套declare namespace jQuery.fn { function extend(object: any): void; }
jQuery.fn.extend({ check: function () { return this.each(function () { this.checked = true; }) } })
声明文件
相比与在 ts 文件中直接书写声明,创建独立的声明文件更便于维护和管理,声明文件以 .d.ts 结尾,如文章开头的 demo.d.ts 。
当然文章开头主要的内容主要用于演示,并不规范,下面我们来看一下声明文件常见的用法。
声明文件通常单独放在
types文件夹内并在tsconfig.json中引入合理使用声明文件有助于使开发同学快速了解“库”的使用方式
第三方声明文件
$ npm i -D @types/[packageName]
// 如
$ npm i -D @types/jquery
注意这里的 @types 是约定的前缀,所有社区的第三方包声明文件都需要使用该前缀
通常主流的外部库都自带了声明文件,并不需要我们再单独安装 @types 文件 这里我们以
view-design为来看
view-design 已包含类型声明文件
NPM 模块声明文件
大多数 NPM 包都自带了声明文件,如果真的有 NPM 包没有声明文件,那就需要我们自己来写了,以下配置表示在我们引入 NPM 包时,会查找 types/ 目录下的声明文件
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": [ "types/*" ]
}
}
}
- 示例如下
上面的图中可以看到,我们安装的 NPM 包
jquery并没有声明文件,此时 TS 自动读取
types/jquery/index.d.ts这样在使用时就有代码提示了
- 我们还可以使用下面的方式来声明
全局扩展
有些时候,我们可以能需要扩展全局对象,如 window
declare global {
interface Window {
jQuery: any;
}
}
window.jQuery = window.jQuery || {};
扩展全局变量的类型
String.prototype.prependHello = function () {
return 'hello' + this;
}
// 下面的代码会有异常提示,因为 String 上并没有 prependHello 方法
// 想要扩展新的属性我们需要先声明
'foo'.prependHello(); // hellofoo
修改后
interface String {
prependHello(): string;
}
String.prototype.prependHello = function () {
return 'hello' + this;
}
'foo'.prependHello(); // hellofoo
上面这种方式在一般场景下是正确的,但在 NPM 包的声明文件中会失效
上面我们有讲过, 对于一个 NPM 包的声明文件,只有
export导出的声明才会有效所以在 NPM 包的声明文件中,我们需要使用下面的写法
declare global {
interface String {
prependHello(): string;
}
}
在
declare global中的声明信息会正常的扩展到全局
声明合并
同一名称的多个独立声明,会被合并为单一声明,合并后的声明拥有原先两个声明的特性
多个声明可以在相同的文件也可以在不同的文件
类型和值
// 定义一个 Animal 类,既可以作为类型使用,也可以作为值使用
class Animal {
name: string;
}
// 作为类型
let b: Animal;
// 作为值
let a = new Animal();
// 与类不同,接口只能作为类型使用
interface People {
name: string;
}
// 作为类型使用
let p: People;
// 不能作为值使用
let q = People; // 报错
声明可以使用的场景
接口合并
上面有说过,统一名称的多个独立声明,会合并为一,接口声明也是声明的一种,也会合并
使用命名空间扩展类、函数和枚举
使用命名空间扩展类
// 我们可以使用 namespace 来扩展类,用于表示内部类
class Person {
label: Person.PersonInfo;
}
// PersonInfo 为 Person 的内部类
namespace Person {
// 为了让 PersonInfo 可被访问,这里需要用 export 导出
export class PersonInfo {}
}
使用命名空间扩展函数
// 我们也可以使用 namespace 来扩展函数
function sayHi(name: string): string {
return sayHi.pre + name + sayHi.next;
}
// 为 sayHi 函数扩展了 pre 和 next 属性
namespace sayHi {
export let pre = "Hello,";
export let next = "nice to meet you!";
}
使用命名空间扩展枚举类型
// demo 出处: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
enmu Color {
red = 1,
green = 2,
blue = 4
}
// 为枚举类型 Color 扩展 mixColor 方法
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue
} else if (colorName == "magenta") {
return Color.red + Color.blue
} else if (colorName == "cyan") {
return Color.green + Color.blue
}
}
}
发布声明类型 - tsc 生成声明文件
一个很常见的场景,我们用 TS 开发一个库,开发完成后需要编译为 JS 提供给别的项目使用,但是编译为 JS 之后就会丢失 TS 中的类型,然后我们还需要再写一份类型声明文件,导致我们做了重复的工作。
自动生成类型声明文件
默认 tsc 编译 TS 代码后并不包含声明文件,我们需要添加配置来使其可以自动生成声明文件
// tsconfig.json
{
"compilerOptions": {
"declaration": true // 该项设置为 true 即可
}
}
发布到 NPM
npm 是 JavaScript 生态圈中的包管理器,有众多的开发者和团队在 npm 上发布了许多优质组件,为 JavaScript 生态圈的繁荣做出了自己的贡献。在这里我们就来学习一下如何将自己的代码发布到 npm 上去。
npm init
首先我们需要初始化一个 npm 模块,我们新建一个目录(目录名请不要包含中文) demo ,在目录下执行
npm init -y // 这里是使用默认参数生成,其它使用方式请自行查找
关联 *.d.ts
如果你为库便写了对应的 *.d.ts 文件,你可以在 types 属性内指定文件所在位置
{
"name": "demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
main字段表示包的主文件,其它字段请自行查找
发布模块
完成包的开发即可发布,但本章节的重点为关联 TS 声明文件,发布模块的详细步骤可以自行查找,基本流程如下
-
模块开发完成
-
注册 NPM 账号 www.npmjs.com/signup
-
本地登录 NPM 账号
npm login -
发布
npm publish
发布到 @types
如果想要发布一个包到 @types,需要提交一个 pull request 到 github.com/DefinitelyT…
具体发布指引可以查看官方的规范文档:definitelytyped.org/guides/cont…