【译】TypeScript 类型声明文件(.d.ts)编写之深入(Definition File Theory: A Deep Dive)

通过结构化模块,为所需的 API 提供精确的原型并不是一件容易的事情。例如:我们可能需要一个模块在被调用时,带上 new 关键字与不带 new 关键字来生成不同的类型,在层级关系中,暴露出不同的命名,并且还在模块对象上具有一些属性。

通过阅读本指南,您将拥有编写复杂定义文件的工具,这些文件将提供友好的 API 表面。 本指南关注模块(或 UMD)库,因为这里的选项更多。

一、关键概念(Key Concepts)

通过理解 TypeScript 的一些关键概念,您可以充分理解如何进行任何形式的定义。

1、类型(Types)

如果您正在阅读本指南,您可能已经大致了解 TypeScript 中的类型。 然而,再明确一下,一种类型可以通过以下形式被引入:

  • 类型别名声明:type sn = number | string;
  • 接口声明:interface I { x: number[]; }
  • 类声明:class C { }
  • 枚举声明:enum E { A, B, C }
  • import 声明指向一个类型

2、值(Values)

与类型一样,您可能已经理解了什么是 Value。 Value 是我们可以在表达式中引用的运行时名称。 例如:let x = 5; 创建一个名为 x 的 Value。
  明确一下通过以下形式创建 Value:

  • letconstvar 声明
  • namespacemodule 声明包含一个 Value
  • enum 声明
  • class 声明
  • import 声明指向一个值
  • function 声明

3、命名空间(Namespaces)

类型可以存在于命名空间中。例如:有 let x: A.B.C 声明,我们说类型 C 来自命名空间 A.B
  这种区别是微妙重要的——在这里,A.B 不一定是必要的类型或值。

二、简单的组合:一个名字,多重含义

给定一个名称 A,我们可以为 A 找到三种不同的含义:一个类型,一个值或一个命名空间。 名称的解释方式取决于其使用的上下文。 例如,在声明 let m:A.A = A; 中,A 首先用作命名空间,然后用作类型名称,然后用作值。 这些含义最终可能指的是完全不同的声明!
  这看起来可能会让人困惑,但只要我们不过分使用,它实际上非常方便。 我们来看看这种组合行为的一些有用的方面。

1、內建组合(Built-in Combinations)

精明的读者会注意到,例如:class 同时出现在 typevalue 清单中。 声明 class C {} 创建了两项内容:

  1. 类型 C——类 C 的实例原型
  2. C——类 C 的构造函数

枚举声明的行为类似。

2、用户组合(User Combinations)

假设我们写了一个模块文件 foo.d.ts

export var SomeVar: { a: SomeType };
export interface SomeType {
  count: number;
}

然后使用它:

import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);

这工作得很好,但我们可以想象 SomeTypeSomeVar 密切相关,所以你希望它们有相同的名字。 我们可以使用组合来以相同的名称显示这两个不同的对象(值和类型)Bar

export var Bar: { a: Bar };
export interface Bar {
  count: number;
}

这为消费代码中的解构提供了一个非常好的机会:

import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);

同样,我们在这里使用 Bar 同时作为类型和值。 请注意,我们不必将 Bar的值声明为 Bar 类型——它们是独立的。

三、高级组合(Advanced Combinations)

某些类型的声明可以在多个声明中组合使用。 例如,class C { }interface C { } 可以共存,并且都为 C 类型提供属性。

只要不产生冲突,这是合法的。 一般的经验法则是:

  • 总是与其他相同名称的值冲突,除非它们被声明为命名空间
  • 如果使用类型别名进行声明类型,如:type s = string,则可能会产生冲突;
  • 命名空间永不冲突。

我们来看看如何使用它。

1、使用 interface 添加(Adding using an interface

我们可以使用另一个 interface 声明将其他成员添加到现有 interface 声明中:

interface Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

这也适用于类:

class Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

请注意,我们不能使用 interface 添加类型别名,如:type s = string;

2、使用 namespace 添加(Adding using a namespace

namespace 声明可用于以任何不会产生冲突的方式,添加新的类型、值和命名空间。
例如,为类添加静态成员:

class C {
}
// ... elsewhere ...
namespace C {
  export let x: number;
}
let y = C.x; // OK

请注意,在这个例子中,我们向 C 的静态端(它的构造函数)添加了一个值。 这是因为我们添加了一个值,并且所有值的容器都是另一个值(类型由命名空间包含,命名空间由其他命名空间包含)。

我们也可以为类添加一个命名空间类型:

class C {
}
// ... elsewhere ...
namespace C {
  export interface D { }
}
let y: C.D; // OK

在这个例子中,直到我们写了命名空间声明之前,没有一个命名空间 CC 作为命名空间的含义,与该类创建的 C类型的含义不冲突。

最后,我们可以使用命名空间声明来执行许多不同的合并。 这不是一个特别实际的例子,但显示了各种有趣的行为:

namespace X {
  export interface Y { }
  export class Z { }
}

// ... elsewhere ...
namespace X {
  export var Y: number;
  export namespace Z {
    export class C { }
  }
}
type X = string;

在本例中,第一个块创建以下名称含义:

  • X(因为 namespace 声明包含了值 Z
  • 命名空间 X(因为 namespace 声明包含了类型 Y
  • 类型 Y 在命名空间 X
  • 类型 Z 在命名空间 X 中(类的实例原型)
  • Z,属于 X 值的属性(类的构造器)

第二个块创建以下名称含义:

  • Ynumber 类型,X 值的属性)
  • 命名空间 Z
  • ZX 值的属性)
  • 类型 C(在命名空间 X.Z 中)
  • CX.Z 值的属性)
  • 类型 X

四、使用 export =import

一个重要的规则,是 exportimport 声明输出或输入它们目标的所有含义

五、参考资料

译自 Definition File Theory: A Deep Dive

(完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容