可以為null的類(lèi)型
TypeScript具有兩種特殊的類(lèi)型震鹉, null和 undefined携龟,它們分別具有值null和undefined. 我們?cè)赱基礎(chǔ)類(lèi)型](./Basic Types.md)一節(jié)里已經(jīng)做過(guò)簡(jiǎn)要說(shuō)明狱窘。 默認(rèn)情況下隐轩,類(lèi)型檢查器認(rèn)為 null與 undefined可以賦值給任何類(lèi)型窗慎。 null與 undefined是所有其它類(lèi)型的一個(gè)有效值图云。 這也意味著惯悠,你阻止不了將它們賦值給其它類(lèi)型,就算是你想要阻止這種情況也不行竣况。 null的發(fā)明者克婶,Tony Hoare,稱它為 價(jià)值億萬(wàn)美金的錯(cuò)誤丹泉。
--strictNullChecks標(biāo)記可以解決此錯(cuò)誤:當(dāng)你聲明一個(gè)變量時(shí)情萤,它不會(huì)自動(dòng)地包含 null或 undefined。 你可以使用聯(lián)合類(lèi)型明確的包含它們:
let s = "foo";
s = null; // 錯(cuò)誤, 'null'不能賦值給'string'
let sn: string | null = "bar";
sn = null; // 可以
sn = undefined; // error, 'undefined'不能賦值給'string | null'
注意摹恨,按照J(rèn)avaScript的語(yǔ)義筋岛,TypeScript會(huì)把 null和 undefined區(qū)別對(duì)待。 string | null晒哄, string | undefined和 string | undefined | null是不同的類(lèi)型睁宰。
可選參數(shù)和可選屬性
使用了 --strictNullChecks,可選參數(shù)會(huì)被自動(dòng)地加上 | undefined:
function f(x: number, y?: number) {
? ? return x + (y || 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null); // error, 'null' is not assignable to 'number | undefined'
可選屬性也會(huì)有同樣的處理:
class C {
? ? a: number;
? ? b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'
類(lèi)型保護(hù)和類(lèi)型斷言
由于可以為null的類(lèi)型是通過(guò)聯(lián)合類(lèi)型實(shí)現(xiàn)揩晴,那么你需要使用類(lèi)型保護(hù)來(lái)去除 null勋陪。 幸運(yùn)地是這與在JavaScript里寫(xiě)的代碼一致:
function f(sn: string | null): string {
? ? if (sn == null) {
? ? ? ? return "default";
? ? }
? ? else {
? ? ? ? return sn;
? ? }
}
這里很明顯地去除了 null,你也可以使用短路運(yùn)算符:
function f(sn: string | null): string {
? ? return sn || "default";
}
如果編譯器不能夠去除 null或 undefined硫兰,你可以使用類(lèi)型斷言手動(dòng)去除诅愚。 語(yǔ)法是添加 !后綴: identifier!從 identifier的類(lèi)型里去除了 null和 undefined:
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ù),因?yàn)榫幾g器無(wú)法去除嵌套函數(shù)的null(除非是立即調(diào)用的函數(shù)表達(dá)式)劫映。 因?yàn)樗鼰o(wú)法跟蹤所有對(duì)嵌套函數(shù)的調(diào)用违孝,尤其是你將內(nèi)層函數(shù)做為外層函數(shù)的返回值。 如果無(wú)法知道函數(shù)在哪里被調(diào)用泳赋,就無(wú)法知道調(diào)用時(shí) name的類(lèi)型雌桑。
類(lèi)型別名
類(lèi)型別名會(huì)給一個(gè)類(lèi)型起個(gè)新名字。 類(lèi)型別名有時(shí)和接口很像祖今,但是可以作用于原始值校坑,聯(lián)合類(lèi)型拣技,元組以及其它任何你需要手寫(xiě)的類(lèi)型。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
? ? if (typeof n === 'string') {
? ? ? ? return n;
? ? }
? ? else {
? ? ? ? return n();
? ? }
}
起別名不會(huì)新建一個(gè)類(lèi)型 - 它創(chuàng)建了一個(gè)新 名字來(lái)引用那個(gè)類(lèi)型耍目。 給原始類(lèi)型起別名通常沒(méi)什么用膏斤,盡管可以做為文檔的一種形式使用。
同接口一樣邪驮,類(lèi)型別名也可以是泛型 - 我們可以添加類(lèi)型參數(shù)并且在別名聲明的右側(cè)傳入:
type Container<T> = { value: T };
我們也可以使用類(lèi)型別名來(lái)在屬性里引用自己:
type Tree<T> = {
? ? value: T;
? ? left: Tree<T>;
? ? right: Tree<T>;
}
與交叉類(lèi)型一起使用莫辨,我們可以創(chuàng)建出一些十分稀奇古怪的類(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;
然而毅访,類(lèi)型別名不能出現(xiàn)在聲明右側(cè)的任何地方沮榜。
type Yikes = Array<Yikes>; // error
接口 vs. 類(lèi)型別名
像我們提到的,類(lèi)型別名可以像接口一樣喻粹;然而蟆融,仍有一些細(xì)微差別。
其一磷斧,接口創(chuàng)建了一個(gè)新的名字振愿,可以在其它任何地方使用。 類(lèi)型別名并不創(chuàng)建新名字—比如弛饭,錯(cuò)誤信息就不會(huì)使用別名冕末。 在下面的示例代碼里,在編譯器中將鼠標(biāo)懸停在 interfaced上侣颂,顯示它返回的是 Interface档桃,但懸停在 aliased上時(shí),顯示的卻是對(duì)象字面量類(lèi)型憔晒。
type Alias = { num: number }
interface Interface {
? ? num: number;
}
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;
另一個(gè)重要區(qū)別是類(lèi)型別名不能被 extends和 implements(自己也不能 extends和 implements其它類(lèi)型)藻肄。 因?yàn)?軟件中的對(duì)象應(yīng)該對(duì)于擴(kuò)展是開(kāi)放的,但是對(duì)于修改是封閉的拒担,你應(yīng)該盡量去使用接口代替類(lèi)型別名嘹屯。
另一方面,如果你無(wú)法通過(guò)接口來(lái)描述一個(gè)類(lèi)型并且需要使用聯(lián)合類(lèi)型或元組類(lèi)型从撼,這時(shí)通常會(huì)使用類(lèi)型別名州弟。
字符串字面量類(lèi)型
字符串字面量類(lèi)型允許你指定字符串必須的固定值。 在實(shí)際應(yīng)用中低零,字符串字面量類(lèi)型可以與聯(lián)合類(lèi)型婆翔,類(lèi)型保護(hù)和類(lèi)型別名很好的配合。 通過(guò)結(jié)合使用這些特性掏婶,你可以實(shí)現(xiàn)類(lèi)似枚舉類(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)做為參數(shù)傳遞,傳入其它值則會(huì)產(chǎn)生錯(cuò)誤雄妥。
Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"'
字符串字面量類(lèi)型還可以用于區(qū)分函數(shù)重載:
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
? ? // ... code goes here ...
}