直達
回顧
第二課的時候為了更好的講解基礎類型, 所以我們講解了一部分高級類型, 比如"接口( interface )" / "聯合類型( | )" / "交叉類型( & )", 本節課我會把剩余高級類型都講完.
知識點摘要
本節課主要關鍵詞為: 自動類型推斷 / 類型斷言 / 類型別名(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];
};
其實不是很復雜, 看了本節課前面前面的內容, 這個很好理解是吧:
- 定義一個支持泛型的類型別名, 傳入類型參數
T
. - 通過
keyof
獲取T
上的鍵值集合. - 用
in
表示循環keyof
獲取的鍵值. - 添加
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;
總結
多寫多練, 很快就上手, 放幾個我用ts寫的項目當做參考, 拋磚引玉, 加油!
手勢庫: https://github.com/any86/any-touch
命令式調用vue組件: https://github.com/any86/vue-create-root
工作中常用的一些代碼片段: https://github.com/any86/useful-utils
一個mini的事件管理器: https://github.com/any86/any-event