接着上次TypeScript進階(一),此次把剩下的知識補充上。數組
類型推論又叫類型推斷。promise
以前對類型推論的基礎作了筆記,此次寫的是多類型聯合和上下文類型。dom
多類型聯合的推論是指根據等號右邊推論出左邊。分佈式
// 多類型聯合 let arr7 = [1, 'abcd']; // 根據右邊的[1, 'abcd'],推斷出arr7是(string | number)[]類型 arr7 = ['a', 4, 'd']; // 這裏再次賦值(元素是number或string)是能夠的 // arr7 = [0, true]; // error
上下文類型的推論是指根據等號左邊推論出右邊。函數
舉例。this
// 上下文類型 window.onmousedown = (mouseEvent) => { // 根據window.onmousedown這個對象,這裏推斷出mouseEvent是MouseEvent類型 console.log(mouseEvent); }
// 類型兼容性,深層次遞歸檢測 interface Geometry { count: number, info: { background: string, faceColor: string } } let geom1: Geometry; geom1 = { count: 10, info: { // 這裏會往下檢測info對象 background: 'black', faceColor: 'lightblue' } }
賦給函數的參數個數要少於函數定義的參數個數。spa
舉個例子。3d
forEach((item, index, arr) => { // ... }); // 這個函數定義了三個參數,但實際上,咱們使用的時候,能夠只傳一個參數 forEach((item) => { // ... });
// 兩個函數的參數類型相同 let xx = (a: string) => 0; let yy = (a: string) => 0; xx = yy; // 沒問題
// 兩個函數的參數類型不一樣 let xx = (a: string) => 0; let yy = (a: number) => 0; xx = yy; // 報錯,由於參數類型不同
const getSum = (arr: number[], callback: (...args: number[]) => number): number => { // 把第一個參數數組傳給回調函數 // 回調函數返回接收了第一個參數的回調函數 return callback(...arr); } const sum1 = getSum([1, 2], (...args: number[]): number => { // 回調函數返回參數數組每一個元素的累加和 return args.reduce((a, b) => { return a + b; }, 0); }); const sum2 = getSum([1, 8, 0], (arg1: number, arg2: number, arg3: number): number => { return arg1 + arg2 + arg3; }); console.log(sum1); // 3 console.log(sum2); // 9
let func1 = (n1: number | string): void => {} let func2 = (n1: number): void => {} // func1 = func2; // error func2 = func1; // right
let x = (): string | number => 0 let y = (): string => 'a' x = y // right // y = x // error
function merge(arg1: number, arg2: number): number function merge(arg1: string, arg2: string): string function merge(arg1: any, arg2: any) { return arg1 + arg2; } console.log(merge('1', '2').length); // 2 function sum(arg1: number, arg2: number): number function sum(arg1: any, arg2: any): any { return arg1 + arg2; } let func = merge; // 這裏func的類型是一個對象接口,會有兩個重載的函數 // func = sum; // error 若是再把sum賦值給func,由於sum少了一個重載函數,因此會報錯
enum Status1 { On, Off }; enum Status2 { Dog, Cat }; let s1 = Status1.On; s1 = 1; // right s1 = 2; // right // s1 = Status2.Dog; // error 不兼容
class A1 { static age: number; constructor(public name: string) {} } class B { static age: string; constructor(public name: string) {} } class C { constructor(public name: number) {} } let a11: A1; let b: B; let c: C; b = new B('Jack'); a11 = b; // 這裏沒問題,類A一、B的實例都是傳一個string類型參數,因此把b賦給a11實際上沒問題 c = new C(10); // a11 = c; // 這裏有問題,類A1的實例傳string類型參數,類C的實例傳number類型參數,因此把c賦給a11有類型兼容問題
class A2 { age: number; constructor(age: number) { this.age = age; } } class B2 extends A2 { constructor(age: number) { super(age) } } class C2 { private age: number constructor(age: number) { this.age = age } } const a22: A2 = new B2(10); // 子類能夠賦值給父類類型的變量 // const a23: A2 = new C2(10); // 類C2含有私有屬性,不能賦值
class A3 { protected age: number; constructor(age: number) { this.age = age; } } class B3 extends A3 { constructor(age: number) { super(age) } } class C3 { protected age: number constructor(age: number) { this.age = age } } const a3: A3 = new B3(10); // 子類能夠賦值給父類類型的變量 // const a32: A3 = new C3(10); // C3類並非A3類的子類,同時age屬性受保護,這裏有問題
interface Data<T> {} let data1: Data<number> let data2: Data<string> data1 = {} data2 = data1; // 這種狀況是能夠的
交叉類型是指多個類型的並集,使用&符號表示。code
const mergeFunc = <T, U>(arg1: T, arg2: U): T & U => { let res = {} as T & U; res = Object.assign(arg1, arg2); return res; } console.log(mergeFunc({ a: 1 }, { b: 'b' })); // {a: 1, b: "b"}
const getLengthFunc = (content: string | number): number => { return typeof content === 'string' ? content.length : content.toString().length; } console.log(getLengthFunc('abcd')); // 4 console.log(getLengthFunc(1111009)); // 7
function isString (value: string | number): value is string { return typeof value === 'string'; } const getLen = (arg: string | number): number => { if(isString(arg)) { return arg.length; } else { return arg.toString().length; } } console.log(getLen('aaaa')); // 4 console.log(getLen(123456789)); // 9
若是是簡單的判斷,能夠直接使用typeof來作類型保護,而若是是比較複雜的判斷,則使用定義函數的方式來進行類型保護。以上例子改寫成使用typeof的類型保護方式以下。對象
const getLen = (arg: string | number): number => { if (typeof arg === 'string') { return arg.length; } else { return arg.toString().length; } } console.log(getLen('aaaa')); // 4 console.log(getLen(123456789)); // 9
typeof只對string、number、boolean、symbol這幾種類型作保護。
typeof只作 === 或 !== 判斷的類型保護,像includes這樣的判斷不能實現類型保護。
class cClass1 { age = 18; constructor() {} } class cClass2 { name = 'Jack'; constructor() {} } function getRandomItem () { return Math.random() > 0.5 ? new cClass1() : new cClass2(); } const r1 = getRandomItem(); if (r1 instanceof cClass1) { console.log('This is a instance of cClass1'); } else { console.log('This is a instance of cClass2'); } // 會根據隨機數的大小,返回相應實例的構造函數log
null和undefined是任何類型的子類型。
// number | undefined 、 number | null 、 number | null | undefined 是不同的 const getSum3 = (a: number, b?: number) => { return a + (b || 0); } console.log(getSum3(1));
// 類型斷言,使用"!"手動指明類型不是null或undefined function getSplitStr (arg: number | null | undefined): string { arg = arg || 0.1; function getRes(prefix: string) { return `${ prefix }_${ arg!.toFixed().toString() }` } return getRes('Y'); } console.log(getSplitStr(7.3)); // Y_7 console.log(getSplitStr(null)); // Y_0 console.log(getSplitStr(undefined)); // Y_0
type Childs<T> = { current: T, child?: Childs<T> } const c9: Childs<object> = { current: {}, child: { current: {} } }
// 類型別名與接口兼容 type Alias = { num: number } interface Inter { num: number } let alias: Alias = { num: 123 } let inter: Inter = { num: 1 } alias = inter; // 沒問題
// 字符串字面量類型 type StrType = 'str_type'; // let str: StrType = 's'; // error let str: StrType = 'str_type' // right
// 字符串字面量的聯合類型 type Direction = 'North' | 'East' | 'West' | 'South'; function getDirectionFirstLetter(direction: Direction) { return direction.substr(0, 1); } console.log(getDirectionFirstLetter('East')); // E
// 數字字面量類型 type Age = 18; interface Info { name: string, age: Age } let p1: Info = { name: 'Tom', age: 18 //這個值必須跟Age類型別名的值一致 } // let p1: Info = { // name: 'Tom', // age: 19 // error }
可辨識聯合的兩要素:一、具備普通的單例類型屬性;二、一個類型別名包含了多個類型的聯合。
interface Square { kind: 'square', size: number } interface Rectangle { kind: 'rectangle', height: number, width: number } interface Circle { kind: 'circle', radius: number } type Shape = Square | Rectangle | Circle; function getArea (s: Shape) { switch (s.kind) { case 'square': return s.size * s.size; break; case 'rectangle': return s.width * s.width; break; case 'circle': return Math.floor(Math.PI) * s.radius ** 2; break; } } console.log(getArea({ kind: 'rectangle', width: 100, height: 100 })); // 10000 console.log(getArea({ kind: 'square', size: 4 })); // 16 console.log(getArea({ kind: 'circle', radius: 3 })); // 27
function assetNever (value: never): never { throw new Error(`Unexpected object ${ value }`); } function getArea (s: Shape): number { switch (s.kind) { case 'square': return s.size * s.size; break; case 'rectangle': return s.width * s.width; break; // case 'circle': return Math.floor(Math.PI) s.radius ** 2; break; default: return assetNever(s) } }
這裏寫一個函數assetNever()用來檢查switch代碼的完整性,若是漏寫了case,default中會檢測到代碼的問題。
使用this返回實例,實現鏈式調用。
class Counter { constructor(public count: number) {} public add (v: number) { this.count += v; return this; } public substract (v: number) { this.count -= v; return this; } } var count1 = new Counter(3); console.log(count1.add(10)); // Counter {count: 13} console.log(count1.add(10).substract(1)); // Counter {count: 22} console.log(count1.add(9).substract(9).add(1)); // Counter {count: 23}
class powCounter extends Counter { constructor(public count: number = 0) { super(count); } pow (v: number) { this.count = this.count ** v; return this; } } var p2 = new powCounter(2); console.log(p2.pow(3)); // powCounter {count: 8} console.log(p2.substract(2)); // powCounter {count: 6}
// keyof interface InfoInterfaceAdvanced { name: string, age: number } let infoProp: keyof InfoInterfaceAdvanced infoProp = 'name' infoProp = 'age'
// 泛型使用索引類型 function getValue<T, K extends keyof T> (obj: T, names: K[]): T[K][] { return names.map((n) => obj[n]); } console.log(getValue({ width: 100, id: 'A001' }, ['width', 'id'])); // [100, "A001"]
function getProperty<O, K extends keyof O>(o: O, key: K): O[K] { return o[key]; }
interface Objs<T> { [key: number]: T } let key1: keyof Objs<number>
interface Type { a: never; b: never; c: string; d: number; e: boolean; f: null; g: undefined; h: object } type Test = Type[keyof Type]
keyof不會查詢出never類型的屬性。
interface InfoInterfaceAdvanced { name: string, age: number } type NameTypes = InfoInterfaceAdvanced['name']; // type NameTypes = string
interface Objs22<T> { [key: string]: T } let key22: Objs22<number>['age']
interface InterInfo { width: number, color: string, height: number } // 映射 type ReadonlyInfoMap<T> = { readonly [P in keyof T]: T[P] } type ReadonlyInfo = ReadonlyInfoMap<InterInfo>;
這樣,類型別名ReadonlyInfo就是由InterInfo映射成的只讀屬性類型列表。
若是屬性是可選,則
type ReadonlyInfoMap<T> = { readonly [P in keyof T]?: T[P] }
type Pick1<T, K extends keyof T> = { [P in K]: T[P] } type keyList = 'width' | 'color'; type ReadonlyInfo4 = Pick1<InterInfo, keyList>;
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick1<T, K> { const res: any = {} keys.map((key) => res[key] = obj[key]); return res; } console.log(pick({ width: 100, radius: '50%' }, ['radius'])); // {radius: "50%"}
也能夠使用內置類型,Readonly、Partial、Pick、Record。
// Readonly type ReadonlyInfo2 = Readonly<InterInfo>;
// Partial type ReadonlyInfo3 = Partial<InterInfo>;
// Pick type ReadonlyInfo4 = Pick<InterInfo, keyList>;
// Record // 把對象屬性值映射成其餘屬性值 function mapObject<K extends string | number, V, U> (obj: Record<K, V>, f: (x: V) => U): Record<K, U> { let res: any = {} for(const key in obj) { res[key] = f(obj[key]); } return res; } const names = { a: 'aa', b: 'bbb', c: 'cccc' }; const len = mapObject(names, (s) => s.length ); console.log(len); // {a: 2, b: 3, c: 4}
舉例。
type Proxy<V> = { get(): V set(newval: V): void } type Proxify<O> = { /** * 把屬性值傳給Proxy做爲Proxy的泛型 * 每一個屬性都帶有get、set */ [K in keyof O]: Proxy<O[K]> } function proxify<O>(obj: O): Proxify<O> { const result = {} as Proxify<O>; for (const key in obj) { // 每一個屬性都帶有set、get result[key] = { get: () => obj[key], set: (newval) => obj[key] = newval } } return result; } let modal = { name: 'cancel', width: 500, msg: 'success', radius: '5%' } let proxyProps = proxify(modal); console.log(proxyProps); // {name: {…}, width: {…}, msg: {…}, radius: {…}} console.log(proxyProps.name.get()); // cancel
// 拆包 function unproxify<O> (obj: Proxify<O>): O{ const result = {} as O; for (const key in obj) { // 每一個屬性還原回原來的屬性值 result[key] = obj[key].get() } return result; } console.log(unproxify(proxyProps)); // {name: "cancel", width: 500, msg: "success", radius: "5%"}
添加修飾符使用+,移除修飾符使用-。
// 移除修飾符 - type RemoveReadonlyInfo<T> = { -readonly [P in keyof T]-?: T[P] } type NotReadonly = RemoveReadonlyInfo<InterInfo>
type MapToPromise<T> = { [K in keyof T]: Promise<T[K]> } type Tuple = [number, string, boolean]; type promiseTuple = MapToPromise<Tuple>; let t1: promiseTuple = [ new Promise((resolve, reject) => {resolve(1)}), // 1 對應T[K] new Promise((resolve, reject) => {resolve('1')}), // '1' 對應T[K] new Promise((resolve, reject) => {resolve(true)}) // true 對應T[K] ]; console.log(t1);
// 一、任何類型均可以賦值給unknown類型 let value2: unknown; value2 = 123; value2 = 'abcd';
let value11: unknown; let value2: unknown; value11 = value2;
let value4: unknown; // value4 += 1; // error
type type1 = string & unknown; // string type type2 = unknown & unknown; // unknown
type type5 = unknown | string; // unknown type type6 = any | unknown; // any type type7 = unknown | number[] // unknown
type type8 = never extends unknown ? true : false; // true
type type9 = keyof unknown; // never
let value11: unknown; let value2: unknown; value11 !== value2 value11 === value2 // value11 += value2; // error
let value: unknown; // value.aa // error // value() // error // new value() // error
type Type11<T> = { [P in keyof T]: number } type type11 = Type11<any>; // { [x: string]: number } type type12 = Type11<unknown>; // {}
形如:
T extends U ? x : y
條件類型舉例。
type Type2<T> = T extends string ? string : number; let index: Type2<'a'> // let index: string let index2: Type2<1> // let index2: number let index3: Type2<false> // let index3: number
type TypeName<T> = T extends any ? T : never; type type13 = TypeName<string | number>; // string | number
type TypeList<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends undefined ? undefined : T extends () => void ? () => void : object type Type14 = TypeList<number[]> // object type Type15 = TypeList<() => void> // () => void type Type16 = TypeList<string> // string
type Diff<T, U> = T extends U ? never : T; type Type17 = Diff<string | number | boolean, undefined | number>; // string | boolean
type Type18<T> = { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T] // keyof不返回never類型的屬性名,除了函數,其餘都是never interface Part { id: number, name: string, subparts: Part[], updatePart(name: string): void } type Type19 = Type18<Part> // updatePart
type Type20<T> = T extends any[] ? T[number] : T; // T[number]能夠看做是返回每一個元素對應的類型 type Type21 = Type20<string[]> // string type Type21 = Type20<(string | number)[]> // string | number type Type22 = Type20<number> // number
type Type20<T> = T extends any[] ? T[number] : T; // 等價於使用infer進行的推斷 type Type23<T> = T extends Array<infer U> ? U : T;
type Etype1 = Exclude<'a' | 'b' | 'c', 'c'>
type Etype2 = Extract<'a' | 'b' | 'c', 'c'>
type Ntype1 = NonNullable<string | number | boolean | null | undefined>;
type Rtype1 = ReturnType<() => string> // string type Rtype1 = ReturnType<() => void> // void
T須要是一個構造函數或者是never、any類型。
type Itype1 = InstanceType<typeof Iclass> // 須要使用typeof標識這個Iclass是個類而不是一個值 type Itype2 = InstanceType<any> type Itype3 = InstanceType<never>