Intersection Types
它是把多個(gè)類(lèi)型組合成一個(gè)類(lèi)型,它使得你使用這個(gè)類(lèi)型的時(shí)候享有定義的所有類(lèi)型的特征漂羊。
例如:
Person & Serializable & Loggable
即你定義的具有這個(gè)類(lèi)型的對(duì)象擁有了這三種類(lèi)型包括的所有成員帐偎。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
Union Types
與Intersection Types
類(lèi)似于集合運(yùn)算里面的并和差的關(guān)系。
Union Types
代表多選一。使用(|)
顿乒,來(lái)分隔類(lèi)型。number | string | boolean
function padLeft(value: string, padding: string | number) {
// ...
}
let indentedString = padLeft("Hello world", true); // errors during compilation
當(dāng)一個(gè)值是Union Types
泽谨,我們只能使用所有類(lèi)型里面公有的屬性璧榄,因?yàn)槠渌膶傩圆淮_實(shí)是否有,所以使用吧雹,TypeScript會(huì)報(bào)錯(cuò)骨杂。
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
Type Guards and Differentiating Types
Union types
對(duì)于建模情形是非常有用的,這種情形下雄卷,他們所帶的類(lèi)型的中有重疊的值搓蚪。
例如:我們想知道在什么情況下是某一特定類(lèi)型,這樣我們就能有條件的使用它丁鹉。
在一般的JavaScript中妒潭,我們判斷是否存在特定的成員來(lái)判斷。
let pet = getSmallPet();
// Each of these property accesses will cause an error
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
然而在TypeScript
中只允許訪問(wèn)類(lèi)型共有的值揣钦。
所以我們可以使用type assertion
let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}
else {
(<Bird>pet).fly();
}
User-Defined Type Guards
因?yàn)樯厦嫖覀冇昧硕啻?code>type assertion雳灾,所以我們需要改進(jìn)。只做一次判斷冯凹,就知道接下來(lái)的分支里面是什么類(lèi)型谎亩。
所以TypeScript
引入了 type guard
在一個(gè)函數(shù)的返回類(lèi)型加上parameterName is Type
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
如果變量的類(lèi)型和源類(lèi)型兼容,也就是確實(shí)是 Fish | Bird
宇姚,那么TypeScript會(huì)把變量的類(lèi)型縮小到特定的類(lèi)型匈庭,也就是我們的斷言類(lèi)型。
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
這樣TypeScript既知道if
分支里面是Fish
空凸,也知道else分支里面是Bird
typeof type guards
當(dāng)我們需要做分支判斷的時(shí)候嚎花,引入上面的padLeft
例子。
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
上面需要自己定義額外的函數(shù)來(lái)判斷原始類(lèi)型呀洲,這樣是很痛苦的紊选;但是這樣的判斷很常用啼止,所以TypeScript提供了typeof guard type
,來(lái)幫助減少工作兵罢。
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
現(xiàn)在你只需要寫(xiě)成上面的樣子就可以了献烦。
typeof guard type
是如下兩種格式:
typeof x === "number"
typeof x !== "number"
其中屬于typeof guard type
原始類(lèi)型只包括:number, string, boolean, symbol
,你也可以自己定義比較其他的類(lèi)型卖词,但是不會(huì)被認(rèn)為是typeof guard type
instanceof type guards
格式如下:
instance instanceof constructor
當(dāng)使用這個(gè)保衛(wèi)類(lèi)型的時(shí)候巩那,TypeScript會(huì)將變量的類(lèi)型按順序縮小到一下兩個(gè)范圍:
- 如果這個(gè)構(gòu)造函數(shù)的原型的類(lèi)型不是
any
,就會(huì)縮小到這個(gè)原型類(lèi)型 - 否則返回這個(gè)構(gòu)造函數(shù)簽名的
union types
類(lèi)型
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// Type is 'SpaceRepeatingPadder | StringPadder'
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // type narrowed to 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // type narrowed to 'StringPadder'
}
Nullable types
TypeScript有兩個(gè)特殊類(lèi)型此蜈。null
和 undefined
即横,他們?cè)跊](méi)有限制的條件下能夠賦值給任何類(lèi)型。
如果加上--strictNullChecks
選項(xiàng)裆赵,只能賦值給各自的類(lèi)型和void
Type guards and type assertions
因?yàn)榭梢詾榭盏念?lèi)型(nullable
)是union type
东囚,所以你需要排除null
,方法和JavaScript中一樣。
- 方法一
function f(sn: string | null): string {
if (sn == null) {
return "default";
}
else {
return sn;
}
}
- 方法二
function f(sn: string | null): string {
return sn || "default";
}
- 方法三战授,如果編譯器不能排除
null
页藻,你就需要用type assertions
來(lái)手動(dòng)排除。
例如在嵌套的函數(shù)中植兰,編譯器不能追蹤到所有調(diào)用(除非這個(gè)嵌套函數(shù)是立即執(zhí)行的(IIFE))份帐,尤其是這個(gè)嵌套函數(shù)作為返回的結(jié)果返回給外面的函數(shù)時(shí)。
不知道在哪里會(huì)調(diào)用這個(gè)嵌套函數(shù)楣导,所以在函數(shù)體執(zhí)行時(shí)废境,也就不不知道參數(shù)的類(lèi)型,所以編譯器無(wú)法排除null
爷辙。
手動(dòng)排除方法:通過(guò)identifier!
排除null
function broken(name: string | null): string {
function postfix(epithet: string) {
return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null
}
name = name || "Bob";
return postfix("great");
}
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob";
return postfix("great");
}
其實(shí)綜上彬坏,就是非立即執(zhí)行的嵌套函數(shù)在調(diào)用時(shí)朦促,由于它存在的那個(gè)函數(shù)里已經(jīng)執(zhí)行完畢膝晾,所以它引用的那個(gè)函數(shù)中的變量類(lèi)型以及不可知了。
Type Aliases
它能給類(lèi)型創(chuàng)建一個(gè)名字务冕。Aliases
不會(huì)創(chuàng)建新類(lèi)型血当,只會(huì)創(chuàng)建一個(gè)新名字來(lái)引用這個(gè)類(lèi)型。
給原始類(lèi)型創(chuàng)建別名不是很有用禀忆,盡管它能用于文檔形式臊旭。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === "string") {
return n;
}
else {
return n();
}
}
type aliases
也可以是一個(gè)泛型。
type Container<T> = { value: T };
我們能在type alias
的屬性中引用自己箩退。構(gòu)建樹(shù)形類(lèi)型离熏。
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
與intersection types
組合出一些非常高級(jí)的類(lèi)型。
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
但是戴涝,我們不能在別名申明語(yǔ)句等號(hào)右邊使用這個(gè)別名滋戳。
type Yikes = Array<Yikes>; // error
Interfaces vs Type Aliases
- 接口創(chuàng)建了一個(gè)新名字钻蔑,而類(lèi)型別名只是返回一個(gè)對(duì)象字面量
- 接口可繼承擴(kuò)展,或被實(shí)現(xiàn)奸鸯。類(lèi)型別名不能咪笑。
因?yàn)橐粋€(gè)理想的軟件是可擴(kuò)展的。所以我們盡可能使用Interface
娄涩,如果在遇到Interface表達(dá)不了的shape
時(shí)窗怒,或者需要使用union types
和tuple type
的時(shí)候,才需要使用類(lèi)型別名蓄拣。
String literal type
- 與
union
,guard type
,alias
一起使用來(lái)獲得一個(gè)類(lèi)枚舉的string
類(lèi)型扬虚。
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
}
else if (easing === "ease-out") {
}
else if (easing === "ease-in-out") {
}
else {
// error! should not pass null or undefined.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
- 也用來(lái)區(qū)分真正函數(shù)和重載函數(shù)
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
Discriminated Unions
與union
,guard type
,alias
一起使用來(lái)構(gòu)建一個(gè)高級(jí)的模式:discriminated unions
,也被成為agged unions
,或algebraic data types
球恤。
discriminated unions
在函數(shù)式編程中很有用孔轴。有些編程語(yǔ)言自動(dòng)區(qū)分unions
,而TypeScript是構(gòu)建在現(xiàn)代JavaScript上的碎捺,所以不自動(dòng)區(qū)分路鹰。
discriminated unions
包括以下三個(gè)要素。
- 有著相同的收厨,字符串字面量的屬性的類(lèi)型集 -
the discriminant.
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
- 用一個(gè)類(lèi)型別名把這些類(lèi)型
union
晋柱, -the union.
type Shape = Square | Rectangle | Circle;
- 通過(guò)
type guards
來(lái)對(duì)這個(gè)相同屬性名下的不通知作判斷。
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}