TS类型体操
keyof any是什么类型?
type AnyKeys = keyof any; // "string" | "number" | "symbol"
由于 any
可以表示任何属性,理论上 keyof any
应该包含所有可能的字符串索引。然而,实际上 TypeScript 的类型系统并不支持无限的类型,因此 keyof any
的结果被特殊处理为 string | number | symbol
。
同理:unknow
type UnknownKeys = keyof unknown; // "string" | "number" | "symbol"
为什么有时用 unknow 代替 any?
未知类型代表任何值。它与 any 类型类似,但更安全,因为对未知值做任何操作都是不合法的. unknow类型的出现就是为了解决any类型污染其他变量的问题.
//any污染问题 let x:any = 'hello'; let y:number; y = x; // 不报错 y * 123 // 不报错 y.toFixed() // 不报错
unknow解决类型污染问题:
-
unknown
类型的变量,不能直接赋值给其他类型的变量,除了unknow和any类型,因为他俩代表所有类型.这就避免了污染问题,从而克服了
any
类型的一大缺点。let v:unknown = 123; let v1:boolean = v; // 报错
-
其次,不能直接调用
unknown
类型变量的方法和属性。因为我们不知道unknow类型有什么属性和方法.let v1:unknown = { foo: 123 }; v1.foo // 报错 let v2:unknown = 'hello'; v2.trim() // 报错 let v3:unknown = (n = 0) => n + 1; v3() // 报错
那么什么时候使用unknow类型呢?
- 当一个类型不确定时使用.或者需要类型缩小时使用.
let a:unknown = 1; if (typeof a === 'number') { let r = a + 10; // 正确 }
有了Symbol类型为什么设计师还设计出了unique symbol类型?
因为Symbol类型包含了所有的Symbol值,但是无法表示某一个具体的Symbol值.但是我们在使用Symbol类型时,都是有且仅使用一个具体的值.这就好比:
Symbol类型相当于string unique symbol类型相当于'a' 我们在创建使用symbol类型时,只会使用类型'a'
Interface 和 type 的不同之处
-
interface
不能包含属性映射(mapping).type
可以.interface Point { x: number; y: number; } // 正确 type PointCopy1 = { [Key in keyof Point]: Point[Key]; }; // 报错 interface PointCopy2 { [Key in keyof Point]: Point[Key]; };
-
...
函数重载
在进行普通函数重载时,例如:
function fn1... function fn1... function fn1... let t = fn1(...) //从最先声明的函数开始匹配
如果是interface接口合并.(声明多个interface可以将多个interface合并成一个,该interface包括之前interface声明的所有属性(彼此不能有类型冲突),如果是函数,将触发函数重载.
)
interface Cloner { clone(animal: Animal): Animal; } interface Cloner { clone(animal: Sheep): Sheep; } interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat; } // 等同于,并且,越靠后的定义,优先级越高,排在函数重载的越前面。 interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat; clone(animal: Sheep): Sheep; clone(animal: Animal): Animal; }
TS中的Infer是什么意思?
infer
关键字用来定义泛型里面推断出来的类型参数,而不是外部传入的类型参数。
它通常跟条件运算符一起使用,用在extends
关键字后面的父类型之中。
键名重映射!!
type A = { foo: number; bar: number; }; type B = { [p in keyof A as `${p}ID`]: number; }; // 等同于 type B = { fooID: number; barID: number; };
TS中可以用模板字符串!
可以根据键名重映射做许多操作.例如:
//属性过滤 type User = { name: string, age: number } type Filter<T> = { [K in keyof T as T[K] extends string ? K : never]: string } type FilteredUser = Filter<User> // { name: string }
TS中的extends关键字用法
用法1: 用来继承. 例如接口和类.
用法2: 用来条件判断.
例如: A extends B
:
如果类型A能够赋值给类型B,那么表达式判断为真,否则为假。
1. 也就是类型A东西要多,类型B东西要少,类型A才能赋值给类型B(类型B = 类型A -> 类型B所需要的东西 类型A一定要有,多了无所谓.).那么表达式为真,否则为假.
2. 也就是A是B的子类,则表达式为真,否则为假.
3. 就像用法1一样:A extends B 表示A是B的子类,A肯定要比B的东西多.
以上3条适用于对象类型的.对于一般类型,例如:true extends boolean 相当于: true extends true|false,这样是对的.
索引签名
索引签名只能用[string,number,symbol]
,使用其他类型会报错.(也不能使用类型作为索引).
-
对于boolean类型,使用索引签名会导致:
type A = [boolean]; type C = { [P in `${A[number]}`]: `${P}`; } //C = {true:true,false:false}
-
对于联合类型,使用索引签名会导致:
type A = 1|2|3 type B = { [p in `${A}`]:true } //B = {1:true,2:true,3:true}
TS中的PromiseLike类型
就是Promise类型.
TS中的泛型
泛型可以在对象/函数中使用:
先定义类型:
//对象 interface ObjType<T> { arg1:T, arg2:T } const obj:ObjType<number> = { arg1:1, arg2:1 } //函数 type FnType<T> = (arg:T) => T const fetch:FnType<number> = (arg)=>{ return arg }
直接在函数中定义:(对象中不能直接定义)
const fetchFn:<T>(arg:T)=>T = (arg) => { return arg; } fetchFn(1);
*Equal是怎么实现的?
Equal是用来判断两个类型是否严格相等.
函数的keyof and extends
对于一个函数类型为:
type FN = () => 22;
其实其可以转换为一个对象:(但是其实是没有这个属性的,可以将这个属性看作 never 类型)
type Fn = { 'someCallName':() => 22; }
因此,keyof Fn = never;
但是:
type ex = FN extends Record<never,any> ? true : false 结果为true type ex = FN extends Record<string,any> ? true : false 结果为true type ex = FN extends Record<number,any> ? true : false 结果为false
很奇怪...
函数的泛型
函数的泛型一般如此表示:
type FnType<T,K> = (key:T,value:K) => void; //或者 function handleFnJS<T,K>(key:T,value:K) { //一些逻辑 }
还有一种函数的表示就是在接口或者对象里面,例如:
interface outType { handle<K,T>(key:K,val:T):string; //或者是 handle:<K,T>(key:K,val:T) => string; }
数组的keyof and extends
对于一个数组[1,2,3..]来说,其本质上是一个对象:
{ 0:1, 1:2, 2:3, pop:()=>...., push:()=>..., `symbol`:xxx }
因此其keyof [1,2,3..] 应该是 string | number | symbol,而对应的值更是五花八门了.
那么为什么:
type Arr = [1, 2, 3, 4]; type ex = Arr extends Record<string,any> ? true : false; 结果会是true呢?
extends判断的是前者是否为后者的子类,很明显,keyof Arr = string | number | symbol.
如果直接keyof Arr extends string,则肯定为false.
但是对于对象来说,子类拥有的属性之类的要比父类多才符合 extends.
declare 关键字
使用方法:
declare const te:TypeTe;
这一段表明te这个变量已经被声明,并且其类型为TypeTe.declare表明该变量一定存在并且一定遵循TypeTe这样一个类型.
类型中如何扩展对象
例如,当你定义了一个对象类型时:
type sc = { [k in string] : number; name:number; }
这时会进行报错:映射的类型可能不声明属性或方法
这时可以通过:
- type定义的可以通过 & {name:number}来进行拓展.
- interface 定义的可以通过extends来进行扩展.