TypeScript - 類型深入

  • 更深入了解 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ù)

思考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容