整理一下思路,
此前工作中,多是用typescript规定一下后端返回的数据类型,一些函数的返回值、参数类型。
今天研究的几个问题:
type和interface的区别?
type关键字只是一个类型别名。和interface不一样。
文档。interface可以重名再写一块来扩展之前定义的同名interface,type只能写一个,不能再打开、拓展了
interface SomeObj{
name1:string
}
interface SomeObj{
name2:number //接口SomeObj会被拓展name2属性,是一个merge的过程,如果property重名则会报错
}
let obj:SomeObj={name1:'hello',name2:2}
console.log(obj)
9.5补充: interface不能: interface A = B|C 它只能声明一个单一的对象结构, type A = B|C 可以的 必须使用type定义类型的联合 interface用extends实现type的&(交叉)
typescript高级类型?
- intersection交叉类型 用&链接要并的类型
注意是取并集,产生新的类型,注意要同时满足交叉的各个类型的要求才可以编译通过;
下面例子里student 如果少了个nameb就会报类型错,说b里有nameb还未出现在对象里
type a= {
name:string
age:number
}
type b={
nameb:string
age:number
}
let student:a&b={
name:'hao',
age:3,
nameb:'wang'
}
和extends有啥区别: 文档
interface PersonA{
name:string //这里name是字符串
}
interface PersonB{
name:number//这里name是number
}
let student: PersonA&PersonB = {name:1}
//typescript对于同名的属性intersection的时候,
//如果该属性类型在各个子类型中不相同,则认为是never
//Type 'number' is not assignable to type 'never'.ts(2322)// The expected type comes from property 'name'
// which is declared here on type 'PersonA & PersonB'//(property) name: never
//但如果有重复的类型,则会使用交集类型,比如PersonA的name 用string|number那么交叉集就会name:number
//同名属性extends的时候呢?
- union. | 联合。 这个变量可能是联合的类型的任选其一。但会发现如果定义的变量满足intersection其实也能编译通过,这是因为交叉类型作为并集,可以赋值到联合类型的各个类型上 所以不会报错. 可辨识discriminated 联合 每个接口具有标识自己的属性和对应的值。interface Rectangle {kind:'rect'}, interface Circle {kind:'circle'}
type a= {
name:string
age:number
}
type b={
nameb:string
age:number
}
let student:a|b={name:'hao',age:3,nameb:'wang'}//编译通过
怎么判断具体是那种类型?
type a= {
name:string
age:number
gendera:string
}
type b={
nameb:string
age:number
genderb:string
}
let student:a|b={name:'hao',age:3,gendera:'male'}
function isB(man:a|b): man is b{
return (<b>man).genderb!==undefined
}
console.log(isB(student))//false
类型谓词 函数参数 is 某个类型
有什么用?
如果想在函数里调用student的gendera或genderb(调用仅属于一个字类型的成员时。会报错
所以需要先判断一下,判断类型的if就是 type guard
let student:a|b={name:'hao',age:3,gendera:'male'}
function isB(man:a|b): man is b{
return (<b>man).genderb!==undefined
}
//Any time `isFish` is called with some variable,
// TypeScript will *narrow* that variable to that specific type if the original type is compatible.
function getManGender(man:a|b){
if(isB(man)){
return man.genderb
}
return man.gendera
}
console.log(getManGender(student))
缩小联合类型的范围的过程叫narrowing,有instanceof、typeof、as等等不同的方法 看文档narrowing
-
type alias
-
number/string literal. 值只能为指定的字符串。经常见的: interface MyObj{ a: 'hello'|'no hello'}
-
索引类型 ----- 鼠掉了,项目用这个感觉真的要鼠掉了
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
//这个type就是number Person是个type,它可以用key来选,得到的是该key对应的type;
//注意,你不能const key="age" 然后Person[key],但你能type key = "age" Person[key]
type I2 = Person[keyof Person]; //这个type是Person['age'|'name'|'alive'],也就是number|string|boolean
用一下
type Person = {age:number; name:string; alive:boolean}
type Age = Person['age']
let my:Age ='a'//Type 'string' is not assignable to type 'number'.ts(2322)
let myf:Age = 0
type Person = {age:number; name:string; alive:boolean}
type MagicType = Person[keyof Person]
let myvar:MagicType = 0
let myvar2:MagicType = false
let myvar3:MagicType = 'a'
let myvar4:MagicType = null //Type 'null' is not assignable to type 'MagicType'.ts(2322)
看个复杂的
function pluck<T, K extends keyof T>(o:T,names:K[]):T[K][]{
return names.map(n=>o[n])
}
拆解一下。
函数pluck,
-
- 接受的参数类型为<T, K extends keyof T>,
- 1.1. K extends keyof T 泛型K的类型约束是keyof T,
- 1.2. 假如T是type Person = {age:number; name:string; alive:boolean}那么K就是"age"|"name"|"alive",那么T[K]就是T["age"|"name"|"alive"],也就是 string|boolean|number
-
- 参数为 o:T,names:K[]
-
- 返回值类型为T[k] 类型构成的数组;结合1.2的例子,其实就是string[]或boolean[]或number[]
function pluck<T, K extends keyof T>(o:T,names:K[]):T[K][]{
return names.map(n=>o[n])
}
type Person ={name:string, age:number, alive:boolean}
let aman = {name:'tom',age:2,alive:true}
console.log(pluck(aman,["name","age"]))//输出['tom',2]
可以看出这个函数的功能就是给定一个字段列表返回T这个类型的对象实例对应字段的值构成的数组。
- this多态类型 这个tsc编译后的代码很有意思。。但不是今天的主题
class BasicCalculator {
public constructor(protected value: number = 0) { }
public currentValue(): number {
return this.value;
}
public add(operand: number): this {
this.value += operand;
return this; // return this来向管道一样继续调用下去
}
public multiply(operand: number): this {
this.value *= operand;
return this;
}
// ... other operations go here ...
}
class ScientificCalculator extends BasicCalculator {
public constructor(value = 0) {
super(value);
}
public sin() {
this.value = Math.sin(this.value);
return this;
}
// ... other operations go here ...
}
let vv= new ScientificCalculator(2)
.multiply(5) //返回this ,继续调用
.sin()//返回this ,继续调用
.add(1)//返回this ,继续调用
.currentValue();
console.log(vv)
还有其他很多。Partial, Pick 。。。。
infer
有这样一个需求,给你一个Promise<不知道啥类型> ,你来定义一个PromiseType,拿出来 不知道啥类型
//define a PromiseType
type PromiseType<T>= ???
type ptype = PromiseType<Promise<string>>
type PromiseType<T> = T extends Promise<infer K>?K:T
传进去泛型T,T是不是promise?不是,那么就返回T。是Promise?那么就要用infer来推断一下Promise<这个里面的类型>,把推断出的结果设为K,因为满足extends,则K 编译通过:
type PromiseType<T> = T extends Promise<infer K>?K:T
type MyPromiseReturnType = PromiseType<Promise<string>>
const a:MyPromiseReturnType = 'a'
type MyPromiseReturnType2 = PromiseType<Promise<number>>
const b:MyPromiseReturnType2 = 1
再来一个,定义一个FirstArg类型,能够拿出来一个函数类型的第一个参数的类型
type FirstArg<T> = T extends (first: infer F, ...args:any[])=>any?F:T;
type FA = FirstArg<(name:string,age:number)=>void>
以此类推,拿出函数返回值的类型:
type PersonHere = {
person:string
}
type MyReturnType<T> = T extends (name:string,age:number)=>infer F?F:T;
type ReturnedType = MyReturnType<(name:string,age:number)=>PersonHere>//注意返回值就直接写个interface名就行了 ,这里只是描述一下类型
const mannn:ReturnedType = {person:'aaa'}
再来一个类型体操。。。缺氧了要
//type ArrayTypeList =?
type ArrayTypeList<T> = T extends (infer I)[]?I: T
type ItemType1 = ArrayTypeList<[string,number]>
const item1:ItemType1='a' // string|number
(infer I)[]