接着上次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>