目录
类型兼容性
对象类型兼容性
接口类型兼容性
函数类型兼容性
索引签名类型
映射类型
索引查询类型
交叉类型
类型兼容性
在TS中,类型采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状。也就是说,在结构化系统中,如果两个对象具有相同形状,则认为他们属于同一类型。
class obj {x: number; y: number}
class obj1 {x: number; y: number}
// 因为TS的结构化类型,只检查obj与obj1的结构是否相同
const p: obj = new obj1()
注意:如果在 Nominal Type System中(比如:C#、java等),它们是不同的类,类型无法兼容。
对象类型兼容性
在结构化系统中,如果两个对象具有相同的形状,则认为他们属于同一类型。这种条件成立的前提在于成员多的可以赋予成员少的,反之则报错。如下:
class obj {x: number; y: number}
class obj1 {x: number; y: number; z: number}
// 成员多的 obj1 可以赋值给成员少的 obj
const p: obj = new obj1()
// const p1: obj1 = new obj() 成员少不能赋值给成员多的,报错
接口类型兼容性
接口类型兼容性,类似于class。并且class和interface之间也可以兼容。
interface person {
name: string
age: number
}
interface person1 {
name: string
age: number
}
interface person2 {
name: string
age: number
say: ()=> void
}
let p1: person = {name:'张三',age:18}
let p2: person1 = {name:'李四',age:17}
let p3: person2 = {name:'王五',age:16,say(){}}
p2=p1
p1=p2
// p3=p1 报错
// 类和接口之间也可以兼容
class person3 {
name: string
age: number
say: ()=> void
}
let p4: person3 = {name:'陈六',age:15,say(){}}
p2=p4
函数类型兼容性
函数类型的兼容性比较复杂,需要考虑以下三种情况:
参数个数:参数多的兼容参数少的,即参数少的可以赋值给参数多的。
type F1 = (a: number) => void
type F2 = (a: number,b: number) => void
let f1: F1 = (a=1)=>{}
let f2: F2
f2=f1
// f1=f2 参数多的不能赋值给参数少的 报错
参数类型:相同位置的参数类型要相同(原始类型)或兼容(对象类型)。
type F1 = (a: number) => void
type F2 = (a: number) => void
let f1: F1 = ()=>{}
let f2: F2 = ()=>{}
f1 = f2
在对象类型中,参数可能很多,这个时候就需要参数多的兼容参数少的,即参数少的能赋值给参数多的,如下:
interface point1 {
name:string
age:number
}
interface point2 {
name:string
age:number
say():void
}
type F1 = (P:point1) => void // 相当于两个参数
type F2 = (P:point2) => void // 相当于三个参数
let f1:F1=()=>{}
let f2:F2
f2=f1
返回值类型:只关注返回值类型本身即可。如果返回值类型是原始类型,类型之间要相同;如果返回值类型是对象类型,成员多的可以赋值给成员少的。
// 原始类型
type F1 = ()=> string
type F2 = ()=> string
let f1: F1 = ()=>{return '1'}
let f2: F2 = f1
// 对象类型
type F3 = {name:string}
type F4 = {name:string,age:number}
let f3: F3
let f4: F4 = {name:'张三',age:18}
f3=f4
索引签名类型
绝大部分情况下,我们可以在使用对象前就确定对象的结构,并为对象添加准确的类型。然而当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时就需要用到索引签名类型。
interface anyObj {
// 使用 [key: string] 来约束该接口允许出现的属性名称。
// key只是一个占位符,可以换成任意合法的名称
[key: string]: number
}
let obj: anyObj = {
// 表示只要是string类型都可以出现在对象中,这样对象就可以出现任意多个字符
a:1,
abc:12,
xzx:1
}
在JS中数组是一类特殊的对象,特殊在数组的键也就是索引是数组类型,并且数组也可以出现任意多个元素。所以在数组对应的泛型接口中,也用到了索引签名类型。
interface MyArr<type>{
[index: number]: type
}
// 符合数组索引必须是 number 这一类型。
let arr: MyArr<number> = [1,2,3]
arr[0]
映射类型
映射类型是基于旧类型创建新类型(对象类型),减少重复、提升开发效率。映射类型是基于索引签名类型的,所以该语法类似于索引签名类型,也使用了 [] 。
注意:映射类型只能在类型别名中使用,不能在接口中使用。
// 普通写法:每次写一个变量就要再一次声明其类型
type Type1 = {x:number;y:number;z:number}
// 映射写法:创建对象的新类型与声明的类型结构完全一致。
type PropKeys = 'x'|'y'|'z'
type Type2 = {[keys in PropKeys]: number}
映射类型除了上文的联合类型创建新类型外,还可以根据对象类型来创建:
type props = {a: number;b: string;c: boolean}
// keyof表示键名是props中所有键名的任意一个。
// 这种方式类似将许多类型统一为同一个类型。
type Type = {[key in keyof props]: number}
索引查询类型
索引查询类型作用是用来查询属性的类型。注意:[]中的属性必须存在于被查询类型中,否则会报错。如下:
type props = {a: number;b: string;c: boolean}
// 查询单个索引类型
type Type = props['a']
// 查询多个索引类型
type Type1 = props['a'|'b']
// 查询所有索引类型
type Type2 = props[keyof props]
交叉类型
交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型),使用教程类型后,新的类型就具备被交叉的类型的所有属性类型。如下:
interface Cat {
name:string
}
interface Dog {
say():void
}
// Animals属性同时具备 接口 Cat和Dog 的所有属性
type Animals = Cat & Dog
let p: Animals = {
name:'毛',
say(){
console.log('汪汪');
}
}
交叉类型(&)与 接口继承(extends)的对比:
相同点:都可以实现对象类型的组合。
不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
当不同的接口类型出现同名属性不兼容的时候,接口继承处理的方法是报错:
interface Cat {
fn:(value:number)=>string
}
interface Dog extends Cat {
fn:(value:string)=>string
}
当不同的接口类型出现同名属性不兼容的时候,交叉类型处理的方法是处理成联合类型:
interface Cat {
fn:(value:number)=>string
}
interface Dog {
fn:(value:string)=>string
}
type Animals = Cat & Dog
// 交叉类型将fn的参数值类型变为联合类型 fn:(value:number|string) => string
// ! 作用强调这个值不为空
let p!: Animals
// 没有报错
p.fn(1)
p.fn('2')