🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] >[success] # 高级类型(二) >[danger] ##### 多态的this类型 ~~~ 1.下面案例使用的链式调用,在1.7版本之前的ts这么写,用PowCounter 时候,使用父类的add 和sub 会报错,新版后这种返回this 会'含类或接口的子类型' ~~~ ~~~ class Counter{ constructor(public count:number=0){} add(value:number){ this.count +=value return this } sub(value:number){ this.count -=value return this } } class PowCounter extends Counter{ pow(value:number){ this.count = this.count ** value return this } } const powCounter = new PowCounter() powCounter.add(10).sub(5).pow(2) ~~~ >[danger] ##### 索引类型 -- 查询操作符 (keyof) ~~~ 1.'keyof T'的结果为T上已知的公共属性名的联合 ~~~ ~~~ interface Info { name:string; age:number; } let info: keyof Info // 相当于let info: "name" | "age" info = 'name' info = 'age' // info = 'w' // 报错 ~~~ * 泛型配合类型索引 ~~~ 1.{1} 说明我们想让'infoValue'返回的都是string 类型的数组,所以age是数字类型因此报错 2.{2} 是我们同过keyof 规定了只能是vauleInfo上存在的key 才行因此'ss' 报错 3.{3} 这个是索引访问操作符的一个用法下面会讲解 ~~~ ~~~ / 现在有个一个需求 取出对象对应key 的value 值 // 这是我还不会用ts 时候工作天天写的 没有合理运用正确ts 写法 // function getValue(obj:any,key:any[]){ // return key.map(item=>obj[item]) // } // 用索引查询累心 和 泛型来写正确写上面的代码逻辑 function getValue<T,K extends keyof T>(obj:T,key:K[]):T[K][]{ {3} return key.map((item)=> obj[item]) } const vauleInfo ={ name:'w', age:18 } let infoValue = getValue(vauleInfo, ['name', 'age']) // let infoValue:string[] = getValue(vauleInfo, ['name', 'age']) // 只返回string 类型的,添加age报错 {1} // getValue(vauleInfo, ['name', 'age','ss']) // 报错valueInfo 属性没有ss {2} ~~~ >[danger] ##### 索引访问操作符 ~~~ 1.可以像js 对象一样直接取出一个一个字段的类型 ~~~ ~~~ // 索引访问操作符 interface Info1 { name: string, age: number } let infos:Info1['name'] // 相当于取出Info1 下角标name字段对应类型 let infos:string // infos =1 // 报错 infos = 'w' // 实际应用 // 相当于只能返回的是T对象规定的值类型 function getProperty<T,K extends keyof T>(obj:T,names:K):T[K]{ return obj[names] } getProperty({name:'w',age:12},'name') // 返回的类型只能是 string 和数字 ~~~ >[danger] ##### keyof 和 索引访问操作 ~~~ 1.来看这个案例首先keyof 取得是key 字段类型 2.索引取得是这个key对应的value类型 ~~~ ~~~ interface Dictionary<T> { [key: string]: T; } let keys: keyof Dictionary<number>; // string let value: Dictionary<number>['foo']; // number ~~~ * 这里是可以通过配置寻该是佛包含 null 和undefined ![](https://img.kancloud.cn/80/79/80792b62d0bccc475c41cd7bee6e19f9_466x62.png) ~~~ interface Type { a:never, b:never, c:null, d:undefined, e:string, f:number, } // 取出所有Type 接口对应的value 的类型 type Test = Type[keyof Type] const valueType:Test = 'w' ~~~ >[danger] ##### 映射类型 ~~~ 1.Readonly -- 所有字段只读映射ts中分装的形式 : type Record<K extends keyof any, T> = { [P in K]: T; }; 2.Partial -- 所有字段可选映射方法ts中分装的形式 : type Partial<T> = { [P in keyof T]?: T[P]; }; 3.Pick -- 挑选指定字段ts中分装的形式 : type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; 4.Record -- 指定key ,value 类型ts中分装的形式 : type Record<K extends keyof any, T> = { [P in K]: T; }; 5infer 、Exclude、 Extract、 NonNullable、 ReturnType 、InstanceType具体使用看下面案例(这些后期可以通过看ts实际实现) 6.ts 的'K in Keys' 写法可以理解成js'for .. in',只不过他是针对类型的,使用这些写法简单小案例: type Keys = 'option1' | 'option2'; type Flags = { [K in Keys]: boolean }; ~~~ * 探讨映射类型出现的原因 ~~~ 1.我们可以用'P in keyof T' 做一个类似循环的字段方式 ~~~ ~~~ // 一个场景 我们现在有一个接口,在一些情况下这些接口值是只能可读的 interface Info1{ age:number; name:string; } const info1:Info1 ={ age:12, name:'w' } // 现在有另外一个变量和info1有相同的属性但只是他的字段都是只读 interface Info2{ readonly age:number, readonly name:string, } const info2:Info2 ={ age:12, name:'w' } // 现在产生一个问题 这样对这种重复字段只是在使用定义不同的内容每次都要做重复操作 // 先使用泛型和类型别名来做一统一转换的方法 type ReadonlyType<T> ={ readonly [P in keyof T]:T[P] } const infos3:ReadonlyType<Info1> = { age:1, name:'w' } ~~~ * ts 提供的只读 和可选 映射方法使用 ~~~ interface Info1 { age: number; name: string; } // 都是只读 const infos3:Readonly<Info1> = { age:1, name:'w' } // 都是可选 const infos4: Partial<Info1> = { age: 1, name: 'w' } ~~~ * ts 提供的选择方法 ~~~ // 这里要注意可选是要填两个位置 interface Info { name:string, age:number, address:string } // 只使用 name 和age 字段 const a:Pick<Info,'name'|'age'> = { name:'w', age:12 } // 当方法需要返回是一个对象时候 const info5 = { name: 'lison', age: 18, address: 'beijing', } // 返回的是一个对象 用pick 来做了指定 function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { const res: any = {} keys.map((key) => { res[key] = obj[key] }) return res } const nameAndAddress = pick(info5, ['name', 'address']) console.log(nameAndAddress) ~~~ * ts Record 记录 * 常见不规范的错 ~~~ // 错误用有时候工作,定义了一个对象类型赋值是一个object // object 没有foo字段会报错 // let bb: object ={ // foo:'s' // } // bb.foo; // k 为任意类型 ,value 为任意类型,解决 let aa: Record<any, any>={ foo:'w' } aa.foo ~~~ * 注意Record两个参数指定的都是类型 ~~~ interface Info { name:string, age:number } // 定义的a key 是Info 接口Key类型,value 是string类型 const a:Record<Info[keyof Info],string> = { name:'w', age:'w' } ~~~ * 一个函数使用小案例 ~~~ function mapObject<K extends string | number, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> { const res: any = {} for (const key in obj) { res[key] = f(obj[key]) } return res } const names = { 0: 'hello', 1: 'world', 2: 'bye' } const lengths = mapObject(names, (s) => s.length) console.log(lengths) ~~~ * 其他的映射方法 ~~~ // infer 推到 type Type8<T> = T extends any[] ? T[number] : T type Test3 = Type8<string[]> type Test4 = Type8<string> type Type9<T> = T extends Array<infer U> ? U : T type Test5 = Type9<string[]> // type Test5 = string 如果是数组取得是数组向中的值 type Test6 = Type9<string> // type Test6 = string // Exclude<T, U> type Type10 = Exclude<'a' | 'b' | 'c', 'a'> // type Type10 = "b" | "c" 排除 a // Extract<T, U> type Type11 = Extract<'a' | 'b' | 'c', 'c' | 'b'|'f'> // type Type11 = "b" | "c" 取两者的交集 // NonNullable<T> type Type12 = NonNullable<string | number | null | undefined> // type Type12 = string | number 排除null 和undefined // ReturnType<T> type Type13 = ReturnType<() => string> // type Type13 = string 将方法返回值作为类型 type Type14 = ReturnType<() => void> // type Type14 = void // InstanceType<T> // 有就是他的构造函数类型没有就是any class AClass { constructor() {} public sum(){} } class BClass { constructor() {} public sum(){} } class DClass { constructor() {} } type T1 = InstanceType<typeof AClass> type T2 = InstanceType<any> type T3 = InstanceType<never> // type T4 = InstanceType<string> // const t1: T1 = new DClass() // 报错 const t2: T1 = new BClass() const t3: T1 = new AClass() const t4: T1 = {sum(){}} ~~~ >[danger] ##### 由映射类型进行推断 ? 这里暂时不懂 ~~~ type Proxy<T> = { get(): T; set(value: T): void; } type Proxify<T> = { [P in keyof T]: Proxy<T[P]> } function proxify<T>(obj: T): Proxify<T> { const result = {} as Proxify<T> for (const key in obj) { result[key] = { get: () => obj[key], set: (value) => obj[key] = value, } } return result } let props = { name: 'lison', age: 18, } let proxyProps = proxify(props) proxyProps.name.set('li') // console.log(proxyProps.name.get()) // -------------- 揭开包装类型----------------- function unproxify<T>(t: Proxify<T>): T { const result = {} as T for (const k in t) { result[k] = t[k].get() } return result } let originalProps = unproxify(proxyProps) ~~~ >[danger] ##### 移除特定修饰符 -- 用'-' 来移除 ~~~ // 移除特定修饰符 readonly 和 ?可以选 interface A { readonly name:string; readonly age:number; } type RemoveA<T> = { -readonly [P in keyof T]-?:T[P] } ~~~ >[danger] ##### 不要思维定式 ~~~ type Tuple = [number, string, boolean] type promiseTuple = MapToPromise<Tuple> let tuple1: promiseTuple = [ new Promise((resolve, reject) => resolve(1)), new Promise((resolve, reject) => resolve('a')), new Promise((resolve, reject) => resolve(false)), ] ~~~ >[danger] ##### unknown ~~~ 1.TypeScript 3.0 引入了新的unknown 类型,它是 any 类型对应的安全类型。 2.unknown 和 any 的主要区别是 unknown 类型会更加严格:在对 unknown 类型的值执行大多数操作之前, 我们必须进行某种形式的检查。而在对 any 类型的值执行操作之前,我们不必进行任何检查。 3.使用any类型,可以很容易地编写类型正确但是执行异常的代码。如果我们使用 any 类型, 就无法享受 TypeScript 大量的保护机制。但如果能有顶级类型也能默认保持安全呢?这就是 unknown 到来的原因 ~~~ ~~~ // unknown // [1] 任何类型都可以赋值给unknown类型 let value1: unknown value1 = 'a' value1 = 123 // [2] 如果没有类型断言或基于控制流的类型细化时,unknown不可以赋值给其他类型,此时他只能赋值给unknown和any类型 let value2: unknown // let value3: string = value2 // 因为value3 是string类型所以 value2不能赋值给value3 value1 = value2 // 可以赋值给any 和unkown 类型 // [3] 如果没有类型断言或基于控制流的类型细化时,不能在他上面进行任何操作 let value4: unknown // value4 += 1 // 没有给具体指定 因此+=1 报错 // [4] unknown与任何其他类型组成的交叉类型,最后都等于其他类型 type type1 = string & unknown // string type type2 = number & unknown // number type type3 = unknown & unknown // unknown type type4 = unknown & string[] // string[] // [5] unknown与任何其他类型(除了any是any)组成的联合类型,都等于unknown类型 type type5 = unknown | string // unknown type type6 = any | unknown // any type type7 = number[] | unknown // unknown // [6] never类型是unknown的子类型 type type8 = never extends unknown ? true : false // [7] keyof unknown 等于类型never type type9 = keyof unknown // never // [8] 只能对unknown进行等或不等操作,不能进行其他操作 // value1 === value2 // value1 !== value2 // value1 += value2 // [9] unknown类型的值不能访问他的属性、作为函数调用和作为类创建实例 let value10: unknown // value10.age // value10() // new value10() // [10] 使用映射类型时如果遍历的是unknown类型,则不会映射任何属性 type Types1<T> = { [P in keyof T]: number } type type11 = Types1<any> // type type11 = {[x: string]: number;} type type12 = Types1<unknown> // type type12 = {} ~~~ >[danger] ##### 条件类型 ~~~ 1.向三运运算动态指定类型 ~~~ ~~~ // T extends U ? X : Y type Types2<T> = T extends string ? string : number // let index: Types2<false> // type TypeName<T> = T extends any ? T : never // type Type3 = TypeName<string | number> type TypeName<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends undefined ? undefined : T extends () => void ? () => void : object type Type4 = TypeName<() => void> type Type5 = TypeName<string[]> type Type6 = TypeName<(() => void) | string[]> type Diff<T, U> = T extends U ? never : T type Test2 = Diff<string | number | boolean, undefined | number> // keyof T 排除never 类型 type Type7<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] interface Part { id: number; name: string; subparts: Part[]; undatePart(newName: string): void } type Test1 = Type7<Part> ~~~