天天看點

為vue3.0學點typescript, 解讀進階類型

知識點摘要

本節課主要關鍵詞為: 自動類型推斷 / 類型斷言 / 類型别名(type) / 映射類型(Pick/Record等...) / 條件類型(extends) / 類型推斷(infer)

自動類型推斷(不用你标類型了,ts自己猜)

第二課我們講了那麼多基礎類型, 大家現在寫ts的時候一定會在每個變量後面都加上類型吧? 但是?

現在告訴大家有些情況下你不需要标注類型, ts可以根據你寫的代碼來自動推斷出類型:

指派字面量給變量
let n = 1; // ts會自動推斷出n是number類型
n+=3 // 不報錯,因為已知類型

let arr1 = []; // 類型為: any[]
arr1.push(1,2,{o:3}); 

let arr = [1]; // 内部要有數字, 才能推斷出正确類型
arr.push(2);      
自動閱讀if條件判斷
let n: number|null = 0.5 < Math.random() ? 1:null;
if(null !== n){
    n+=3 // ts知道現在n不是null是number
}      
浏覽器自帶api
document.ontouchstart = ev=>{ // 能自動推斷出ev為TouchEvent
    console.log(ev.touches);  // 不報錯, TouchEvent上有touches屬性
}      
typeof

​typeof​

​​就是js中的​

​typeof​

​​, ts會根據你代碼中出現的​

​typeof​

​來自動推斷類型:

let n:number|string = 0.5 < Math.random()? 1:'1';

// 如果沒有typeof, n*=2會報錯, 提示沒法推斷出目前是number類型, 不能進行乘法運算
if('number' === typeof n) {
    n*= 2;
} else  {
    n= '2';
}      

注意: 在ts文檔中, 該部分的知識點叫做typeof類型保護, 和其他類型推斷的内容是分開的, 被寫在進階類型/類型保護章節中.

instanceof

ts會根據你代碼中出現的​

​instanceof​

​來自動推斷類型:

let obj = 0.5 < Math.random() ? new String(1) : new Array(1);
if(obj instanceof String){
    // obj推斷為String類型
    obj+= '123'
} else {
    // obj為any[]類型
    obj.push(123);
}      

注意: 在ts文檔中, 該部分的知識點叫做instanceof類型保護, 和其他類型推斷的内容是分開的, 被寫在進階類型/類型保護章節中.

類型斷言(你告訴ts是什麼類型, 他都信)

有些情況下系統沒辦法自動推斷出正确的類型, 就需要我們标記下, 斷言有2種文法, 一種是通過"<>", 一種通過"as", 舉例說明:

let obj = 0.5 < Math.random() ? 1 : [1]; // number|number[]

// 斷言, 告訴ts, obj為數組
(<number[]>obj).push(1);

//等價
(obj as number[]).push(1);      

類型别名(type)

類型别名可以表示很多接口表示不了的類型, 比如字面量類型(常用來校驗取值範圍):

type A = 'top'|'right'|'bottom'|'left'; // 表示值可能是其中的任意一個
type B = 1|2|3;
type C = '紅'|'綠'|'黃';
type D = 150;

let a:A = 'none'; // 錯誤, A類型中沒有'none'      
更多組合:
interface A1{
    a:number;
}
type B = A1 | {b:string};
type C = A1 & {b:string};

// 與泛型組合
type D<T> = A1 | T[];      

索引類型(keyof)

js中的​

​Object.keys​

​​大家肯定都用過, 擷取對象的鍵值, ts中的​

​keyof​

​和他類似, 可以用來擷取對象類型的鍵值:

type A = keyof {a:1,b:'123'} // 'a'|'b'
type B = keyof [1,2] // '1'|'2'|'push'... , 擷取到内容的同時, 還得到了Array原型上的方法和屬性(實戰中暫時沒遇到這種需求, 了解即可)      

可以獲得鍵值, 也可以擷取對象類型的值的類型:

type C = A['a'] // 等于type C = 1;
let c:C = 2 // 錯誤, 值隻能是1      

映射類型(Readonly, Pick, Record等...)

映射類型比較像修改類型的工具函數, 比如​

​Readonly​

​可以把每個屬性都變成隻讀:

type A  = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}      

打開node_modules/typescript/lib檔案夾可以找到​

​lib.es5.d.ts​

​​, 在這我們能找到​

​Readonly​

​的定義:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};      

其實不是很複雜, 看了本節課前面前面的内容, 這個很好了解是吧:

  1. 定義一個支援泛型的類型别名, 傳入類型參數​

    ​T​

    ​.
  2. 通過​

    ​keyof​

    ​​擷取​

    ​T​

    ​上的鍵值集合.
  3. 用​

    ​in​

    ​​表示循環​

    ​keyof​

    ​擷取的鍵值.
  4. 添加​

    ​readonly​

    ​标記.
Partial<T>, 讓屬性都變成可選的
type A  = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}      
Required<T>, 讓屬性都變成必選
type A  = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}      
Pick<T,K>, 隻保留自己選擇的屬性, U代表屬性集合
type A  = {a:number, b:string}
type A1 = Pick<A, 'a'> //  {a:number}      
Omit<T,K> 實作排除已選的屬性
type A  = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}      
Record<K,T>, 建立一個類型,T代表鍵值的類型, U代表值的類型
type A1 = Record<string, string> // 等價{[k:string]:string}      
Exclude<T,U>, 過濾T中和U相同(或相容)的類型
type A  = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number

// 相容
type A2 = Exclude<number|string, any|number[]> // never , 因為any相容number, 是以number被過濾掉      
Extract<T,U>, 提取T中和U相同(或相容)的類型
type A  = {a:number, b:string}
type A1 = Extract<number|string, string|number[]> // string      
NonNullable<T>, 剔除T中的undefined和null
type A1 = NonNullable<number|string|null|undefined> // number|string      
ReturnType<T>, 擷取T的傳回值的類型
type A1= ReturnType<()=>number> // number      
InstanceType<T>, 傳回T的執行個體類型

ts中類有2種類型, 靜态部分的類型和執行個體的類型, 是以​

​T​

​​如果是構造函數類型, 那麼​

​InstanceType​

​可以傳回他的執行個體類型:

interface A{
    a:HTMLElement;
}

interface AConstructor{
    new():A;
}

function create (AClass:AConstructor):InstanceType<AConstructor>{
    return new AClass();
}      
Parameters<T> 擷取函數參數類型

傳回類型為元祖, 元素順序同參數順序.

interface A{
    (a:number, b:string):string[];
}

type A1 = Parameters<A> // [number, string]      
ConstructorParameters<T> 擷取構造函數的參數類型

和​

​Parameters​

​​類似, 隻是​

​T​

​這裡是構造函數類型.

interface AConstructor{
    new(a:number):string[];
}

type A1 = ConstructorParameters<AConstructor> // [number]      

extends(條件類型)

T extends U ? X : Y      

用來表示類型是不确定的, 如果​

​U​

​​的類型可以表示​

​T​

​​, 那麼傳回​

​X​

​​, 否則​

​Y​

​. 舉幾個例子:

type A =  string extends '123' ? string :'123' // '123'
type B =  '123' extends string ? string :123 // string      

明顯​

​string​

​​的範圍更大, ​

​'123'​

​​可以被​

​string​

​表示, 反之不可.

infer(類型推斷)

單詞本身的意思是"推斷", 實際表示在​

​extends​

​​條件語句中聲明待推斷的類型變量. 我們上面介紹的映射類型中就有很多都是ts在​

​lib.d.ts​

​​中實作的, 比如​

​Parameters​

​:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;      

上面聲明一個​

​P​

​​用來表示​

​...args​

​​可能的類型, 如果​

​(...args: infer P)​

​​可以表示 ​

​T​

​​, 那麼傳回​

​...args​

​​對應的類型, 也就是函數的參數類型, 反之傳回​

​never​

​.

注意: 開始的​

​T extends (...args: any) => any​

​​用來校驗輸入的​

​T​

​​是否是函數, 如果不是ts會報錯, 如果直接替換成​

​T​

​​不會有報錯, 會一直傳回​

​never​

​.

應用infer

接下來我們利用​

​infer​

​來實作"删除元祖類型中第一個元素", 這常用于簡化函數參數​

export type Tail<Tuple extends any[]> = ((...args: Tuple) => void) extends ((a: any, ...args: infer T) => void) ? T : never;      

繼續閱讀