- 更深入了解 TypeScript 類型系統(tǒng)
- 通過 聯(lián)合類型、交叉類型、字面量類型 來滿足更多的標(biāo)注需求
- 通過 類型別名、類型推導(dǎo)簡(jiǎn)化標(biāo)注操作
- 通過 類型斷言、類型操作符、類型保護(hù) 來縮小類型標(biāo)注范圍
類型深入
前面我們學(xué)習(xí)了類型標(biāo)注的基礎(chǔ),針對(duì)一些更為特殊情況,TypeScript 也提供了一些方法
聯(lián)合類型
聯(lián)合類型也可以稱為多選類型,當(dāng)我們希望標(biāo)注一個(gè)變量為多個(gè)類型之一時(shí)可以選擇聯(lián)合類型標(biāo)注,或 的關(guān)系
標(biāo)注語法
變量: 類型一 | 類型二
function css(ele: Element, attr: string, value: string|number) {
// ...
}
let box = document.querySelector('.box');
// document.querySelector 方法返回值就是一個(gè)聯(lián)合類型
if (box) {
// ts 會(huì)提示有 null 的可能性,加上判斷更嚴(yán)謹(jǐn)
css(box, 'width', '100px');
css(box, 'opacity', 1);
css(box, 'opacity', [1,2]); // 錯(cuò)誤
}
交叉類型
交叉類型也可以稱為合并類型,可以把多種類型合并到一起成為一種新的類型,并且 的關(guān)系
標(biāo)注語法
變量: 類型一 & 類型二
如,對(duì)一個(gè)對(duì)象進(jìn)行擴(kuò)展:
interface o1 {x: number, y: string};
interface o2 {z: number};
let o: o1 & o2 = Object.assign({}, {x:1,y:'2'}, {z: 100});
字面量類型
有的時(shí)候,我們希望標(biāo)注的不是某個(gè)類型,而是一個(gè)固定值,就可以使用字面量類型,配合聯(lián)合類型會(huì)更有用
function setPosition(ele: Element, direction: 'left' | 'top' | 'right' | 'bottom') {
// ...
}
box && setDirection(box, 'bottom');
box && setDirection(box, 'hehe'); // 錯(cuò)誤
類型別名
有的時(shí)候類型標(biāo)注比較復(fù)雜,這個(gè)時(shí)候我們可以類型標(biāo)注起一個(gè)相對(duì)簡(jiǎn)單的名字
語法
type 新的類型名稱 = 類型
如前面說到的對(duì)象字面類型標(biāo)注
type dir = 'left' | 'top' | 'right' | 'bottom';
function setPosition(ele: Element, direction: dir) {
// ...
}
類型推導(dǎo)
每次都顯式標(biāo)注類型會(huì)比較麻煩,TypeScript 提供了一種更加方便的特性:類型推導(dǎo)。TypeScript 編譯器會(huì)根據(jù)當(dāng)前上下文自動(dòng)的推導(dǎo)出對(duì)應(yīng)的類型標(biāo)注,這個(gè)過程發(fā)生在:
- 初始化變量
- 設(shè)置函數(shù)默認(rèn)參數(shù)值
- 返回函數(shù)值
// 自動(dòng)推斷 x 為 number
let x = 1;
// 不能將類型“"a"”分配給類型“number”
x = 'a';
類型斷言
有的時(shí)候,我們可能標(biāo)注一個(gè)更加精確的類型(縮小類型標(biāo)注范圍),比如:
let img = document.querySelector('#img');
我們可以看到 img的類型為 Element,而 Element 類型其實(shí)只是元素類型的通用類型,如果我們?nèi)ピL問 src 這個(gè)屬性是有問題的,我們需要把它的類型標(biāo)注得更為精確:HTMLImageElement 類型,這個(gè)時(shí)候,我們就可以使用類型斷言,它類似于一種 類型轉(zhuǎn)換:
let img = <HTMLImageElement>document.querySelector('#img');
或者
let img = document.querySelector('#img') as HTMLImageElement;
<span style="color:red">注意:斷言只是一種預(yù)判,并不會(huì)數(shù)據(jù)本身產(chǎn)生實(shí)際的作用,即:類似轉(zhuǎn)換,但并非真的轉(zhuǎn)換了</span>
類型操作符
typeof
獲取值的類型,注:typeof 操作的是值
let colors = {
color1: 'red',
color2: 'blud'
};
type tColors = typeof colors;
/**
tColors 類型
type tColors = {
color1: string;
color2: string;
}
*/
let color2: tColors;
keyof
獲取類型的所對(duì)應(yīng)的類型的 key 的集合,返回值是 key 的聯(lián)合類型,注:keyof 操作的是類型
interface Person {
name: string;
age: number;
};
type personKeys = keyof Person;
// 等同:type personKeys = "name" | "age"
let p1 = {
name: 'zMouse',
age: 35
}
function getPersonVal(k: personKeys) {
return p1[k];
}
/**
等同:
function getPersonVal(k: 'name'|'age') {
return p1[k];
}
*/
getPersonVal('name'); //正確
getPersonVal('gender'); //錯(cuò)誤
in
in 操作符對(duì)值和類型都可以使用
針對(duì)值進(jìn)行操作,用來判斷值中是否包含指定的key
console.log( 'name' in {name:'zmouse', age: 35} );
console.log( 'gender' in {name:'zmouse', age: 35} );
針對(duì)類型進(jìn)行操作的話,內(nèi)部使用的 for…in 對(duì)類型進(jìn)行遍歷
interface Person {
name: string;
age: number;
}
type personKeys = keyof Person;
type newPerson = {
[k in personKeys]: number;
/**
等同 [k in 'name'|'age']: number;
也可以寫成
[k in keyof Person]: number;
*/
}
/**
type newPerson = {
name: number;
age: number;
}
*/
注意:in 后面的類型值必須是 string 或者 number 或者 symbol
extends
類型繼承操作符
interface type1 {
x: number;
y: number;
}
interface type2 extends type1 {}
或者
type type1 = {
x: number;
y: number;
}
function fn<T extends type1>(args: T) {}
fn({x:1, y: 2});
類型保護(hù)
有的時(shí)候,值的類型并不唯一,比如一個(gè)聯(lián)合類型的參數(shù),這個(gè)時(shí)候,在該參數(shù)使用過程中只能調(diào)用聯(lián)合類型都有的屬性和方法
function toUpperCase(arg: string|string[]) {
arg.length; // 正確
arg.toUpperCase(1); // 錯(cuò)誤
// 即使作為條件判斷也不行
if (arg.substring) {
arg.substring(1);
}
}
可以使用類型斷言
if ((<string>arg).substring) {
(<string>arg).substring(1);
}
但是這樣做還是很麻煩的,其實(shí)在TypeScript 中,提供了一個(gè)類型保護(hù)措施來幫助更加方便的處理這樣的情況
typeof
if (typeof arg === 'string') {
arg.substring(1);
} else {
arg.push('1');
}
typescript 能夠把typeof 識(shí)別為類型保護(hù),作為類型檢查的依據(jù),不僅僅是在 if 中有效,在 else 中也是有效的
instanceof
typescript 中的 instanceof 也是類型保護(hù)的,針對(duì)細(xì)化的對(duì)象類型判斷可以使用它來處理
if (arg instanceof Array) {
arg.push('1');
}
自定義類型保護(hù)
有的時(shí)候,判斷并不是基于數(shù)據(jù)類型或者構(gòu)造函數(shù)來完成的,那么就可以自定義類型保護(hù)
function canEach(data: Element[]|NodeList|Element): data is Element[]|NodeList {
return (<NodeList>data).forEach !== undefined;
}
function fn2(elements: Element[]|NodeList|Element) {
if ( canEach(elements) ) {
elements.forEach(_=>{});
} else {
elements.classList.add('box');
}
}
data is Element[]|NodeList 是一種類型謂詞,格式為:xx is type ,返回這種類型的函數(shù)就可以被 TypeScript 識(shí)別為類型保護(hù)
總結(jié)
- 聯(lián)合類型
- 交叉類型
- 字面量類型
- 類型別名
- 類型推導(dǎo)
- 類型斷言
- 類型操作符
- 類型保護(hù)