通過結(jié)構(gòu)化模塊床未,為所需的 API 提供精確的原型并不是一件容易的事情烫映。例如:我們可能需要一個模塊在被調(diào)用時袱衷,帶上 new
關(guān)鍵字與不帶 new
關(guān)鍵字來生成不同的類型检吆,在層級關(guān)系中,暴露出不同的命名曹洽,并且還在模塊對象上具有一些屬性鳍置。
通過閱讀本指南,您將擁有編寫復(fù)雜定義文件的工具送淆,這些文件將提供友好的 API 表面税产。 本指南關(guān)注模塊(或 UMD)庫,因為這里的選項更多偷崩。
一砖第、關(guān)鍵概念(Key Concepts)
通過理解 TypeScript 的一些關(guān)鍵概念,您可以充分理解如何進(jìn)行任何形式的定義环凿。
1梧兼、類型(Types)
如果您正在閱讀本指南,您可能已經(jīng)大致了解 TypeScript 中的類型智听。 然而羽杰,再明確一下,一種類型可以通過以下形式被引入:
- 類型別名聲明:
type sn = number | string;
- 接口聲明:
interface I { x: number[]; }
- 類聲明:
class C { }
- 枚舉聲明:
enum E { A, B, C }
-
import
聲明指向一個類型
2到推、值(Values)
與類型一樣考赛,您可能已經(jīng)理解了什么是 Value。 Value 是我們可以在表達(dá)式中引用的運(yùn)行時名稱莉测。 例如:let x = 5;
創(chuàng)建一個名為 x
的 Value颜骤。
明確一下通過以下形式創(chuàng)建 Value:
-
let
、const
捣卤、var
聲明 -
namespace
或module
聲明包含一個 Value -
enum
聲明 -
class
聲明 -
import
聲明指向一個值 -
function
聲明
3忍抽、命名空間(Namespaces)
類型可以存在于命名空間中八孝。例如:有 let x: A.B.C
聲明,我們說類型 C
來自命名空間 A.B
鸠项。
這種區(qū)別是微妙而重要的——在這里干跛,A.B
不一定是必要的類型或值。
二祟绊、簡單的組合:一個名字楼入,多重含義
給定一個名稱 A
,我們可以為 A
找到三種不同的含義:一個類型牧抽,一個值或一個命名空間嘉熊。 名稱的解釋方式取決于其使用的上下文。 例如扬舒,在聲明 let m:A.A = A;
中阐肤,A
首先用作命名空間,然后用作類型名稱呼巴,然后用作值。 這些含義最終可能指的是完全不同的聲明御蒲!
這看起來可能會讓人困惑衣赶,但只要我們不過分使用,它實際上非常方便厚满。 我們來看看這種組合行為的一些有用的方面府瞄。
1、內(nèi)建組合(Built-in Combinations)
精明的讀者會注意到碘箍,例如:class
同時出現(xiàn)在 type 和 value 清單中遵馆。 聲明 class C {}
創(chuàng)建了兩項內(nèi)容:
- 類型
C
——類C
的實例原型 - 值
C
——類C
的構(gòu)造函數(shù)
枚舉聲明的行為類似。
2丰榴、用戶組合(User Combinations)
假設(shè)我們寫了一個模塊文件 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);
這工作得很好货邓,但我們可以想象 SomeType
和 SomeVar
密切相關(guān),所以你希望它們有相同的名字四濒。 我們可以使用組合來以相同的名稱顯示這兩個不同的對象(值和類型)Bar
:
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
這為消費(fèi)代碼中的解構(gòu)提供了一個非常好的機(jī)會:
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
同樣换况,我們在這里使用 Bar
同時作為類型和值。 請注意盗蟆,我們不必將 Bar
的值聲明為 Bar
類型——它們是獨立的戈二。
三、高級組合(Advanced Combinations)
某些類型的聲明可以在多個聲明中組合使用喳资。 例如觉吭,class C { }
和 interface C { }
可以共存,并且都為 C
類型提供屬性仆邓。
只要不產(chǎn)生沖突鲜滩,這是合法的伴鳖。 一般的經(jīng)驗法則是:
- 值總是與其他相同名稱的值沖突,除非它們被聲明為命名空間绒北;
- 如果使用類型別名進(jìn)行聲明類型黎侈,如:
type s = string
,則可能會產(chǎn)生沖突闷游; - 命名空間永不沖突峻汉。
我們來看看如何使用它。
1脐往、使用 interface
添加(Adding using an interface
)
我們可以使用另一個 interface
聲明將其他成員添加到現(xiàn)有 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
聲明可用于以任何不會產(chǎn)生沖突的方式,添加新的類型梅尤、值和命名空間柜思。
例如,為類添加靜態(tài)成員:
class C {
}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK
請注意巷燥,在這個例子中赡盘,我們向 C
的靜態(tài)端(它的構(gòu)造函數(shù))添加了一個值。 這是因為我們添加了一個值缰揪,并且所有值的容器都是另一個值(類型由命名空間包含陨享,命名空間由其他命名空間包含)。
我們也可以為類添加一個命名空間類型:
class C {
}
// ... elsewhere ...
namespace C {
export interface D { }
}
let y: C.D; // OK
在這個例子中钝腺,直到我們寫了命名空間聲明之前抛姑,沒有一個命名空間 C
。C
作為命名空間的含義艳狐,與該類創(chuàng)建的 C
的值或類型的含義不沖突定硝。
最后,我們可以使用命名空間聲明來執(zhí)行許多不同的合并毫目。 這不是一個特別實際的例子喷斋,但顯示了各種有趣的行為:
namespace X {
export interface Y { }
export class Z { }
}
// ... elsewhere ...
namespace X {
export var Y: number;
export namespace Z {
export class C { }
}
}
type X = string;
在本例中,第一個塊創(chuàng)建以下名稱含義:
- 值
X
(因為namespace
聲明包含了值Z
) - 命名空間
X
(因為namespace
聲明包含了類型Y
) - 類型
Y
在命名空間X
中 - 類型
Z
在命名空間X
中(類的實例原型) - 值
Z
蒜茴,屬于X
值的屬性(類的構(gòu)造器)
第二個塊創(chuàng)建以下名稱含義:
- 值
Y
(number
類型星爪,X
值的屬性) - 命名空間
Z
- 值
Z
(X
值的屬性) - 類型
C
(在命名空間X.Z
中) - 值
C
(X.Z
值的屬性) - 類型
X
四、使用 export =
或 import
一個重要的規(guī)則粉私,是 export
和 import
聲明輸出或輸入它們目標(biāo)的所有含義顽腾。
五、參考資料
譯自 Definition File Theory: A Deep Dive
(完)