类型兼容性
类型兼容性主要是通过赋值和传参来体现的,比如把一个number类型变量赋值给一个string肯定是不兼容的,那其他类型相互之间的兼容性如何呢?
基础类型
如下表所示:
any类型除了不能赋值给never与其他所有类型兼容unknow可以被其他所有类型赋值- 除
any和unknow外,void不可赋值给其他类型 - 不开启
--strictNullChecks选项时,void可被null、undefined、never赋值 - 开启
--strictNullChecks选项时,void可被undefined和never赋值
对象
TypeScript使用结构化的类型检查系统,即判断两个类型是否兼容,只需要判断他们的“结构”是否一致,也就是结构属性名和类型是否一致。
两个拥有相同结构的类型,子类型可以赋值给父类型,反之报错。
interface Animal {
age: number
}
interface Dog extends Animal {
bark(): void
}
declare let animal: Animal
declare let dog: Dog
animal = dog
dog = animal // error: Property 'bark' is missing in type 'Animal' but required in type 'Dog'.
如上子类型dog可以赋值给父类型,反之报错。这样的设计是处于类型安全的考虑。animal这个变量只有age属性,被使用时也只能访问或修改其age属性,而dog子类型也拥有age属性,所以赋值给animal后,不会影响原有逻辑。反之,dog上有一些animal不存在的属性,如果用户对这些属性做了操作,将animal赋值给dog就会报错,这是类型不安全的。
数组同理
declare let animals: Animal[]
declare let dogs: Dog[]
animals = dogs
dogs = animals // Property 'bark' is missing in type 'Animal' but required in type 'Dog'
函数赋值
declare let visitAnimal: (animal: Animal) => void
declare let visitDog: (dog: Dog) => void
visitDog = visitAnimal
visitAnimal = visitDog // Property 'bark' is missing in type 'Animal' but required in type 'Dog'.
如上,发现参数类型是子类型的赋值给参数类型是父类型的发生了报错。
之所以这样设计是处于类型安全的角度考虑,试想如下实现
let visitAnimal = (animal: Animal) => {
animal.age
}
let visitDog = (dog: Dog) => {
dog.age
dog.bark()
}
如果将visitAnimal赋值为visitDog(visitAnimal = visitDog) ,我们依然期望传递一个Animal类型的参数给visitAnimal,而这个参数可能是不带bark属性的,这样就会报错。
visitAnimal = visitDog
let animal = { age: 5 }
visitAnimal(animal) // 报错
反之,如果将visitDog赋值为visitAnimal(visitDog = visitAnimal),我们依然期望传递一个Dog类型的参数给visitDog,由于Dog类型是Animal的子类型,所以赋值后也不会报错,类型是兼容的。
visitDog = visitAnimal
let dog = { age: 5, bark: () => 'bark' }
visitDog(dog) // 不会报错
此外,在 TypeScript 中,由于灵活性等权衡,对于函数参数默认的处理是双向协变 的。也就是既可以 visitAnimal = visitDog,也可以 visitDog = visitAnimal。在开启了 tsconfig 中的 --strictFunctionType 后才会严格按照如上例所示来约束赋值关系.
同样,也有如下情况。函数fun1的参数类型比fun2更具体,是其的子类型,所以fun1不能赋值给fun2。
declare let fun1: (value: any, extraData: any) => void
declare let fun2: (value: any) => void
fun1 = fun2
fun2 = fun1 // error: Type '(value: any, extraData: any) => void' is not assignable to type '(value: any) => void'.
联合类型赋值
如下,对于联合类型而言'a' | 'b'比'a' | 'b' | 'c'更具体,所以前者是后者的子类型。
type Parent = 'a' | 'b' | 'c'
type Son = 'a' | 'b'
let parent: Parent
let son: Son
parent = son
son = parent // error: parent 有可能是 'c'
枚举
对于枚举值,枚举值和number互相兼容,而不同枚举类型的枚举值不兼容。
enum Status {
Ready,
Waiting,
}
enum Color {
Red,
Blue,
Green,
}
declare let num: number
let ready = Status.Ready;
ready = Color.Green; // Type 'Color.Green' is not assignable to type 'Status'
ready = num
num = ready
总结
TS中,函数赋值,参数要求是逆变的,即父类型参数可以赋值给子类型参数,反之不可。其他情况都是子类型可赋值给父类型。
ThisType
对象调用
方法作为对象的属性时,其内部this会被自动推导
const obj1 = {
name: "name",
getNam() {
return this.name // 可以自动推导为{ name:string, getName():string}类型
},
}
// 使用箭头函数会报错
const obj1 = {
name: "name",
getNam: () => {
return this.name // error: The containing arrow function captures the global value of 'this'
},
}
构造器调用
typescript不支持es5构造函数的类型推断,也就是以下写法会报错。
如果开启了--noImplicitThis选项,在es5的构造函数内使用this会报错;此外,也不能通过new操作符来调用。
建议直接使用es6的class语法,this会被正确推断
function People(name: string) {
this.name = name // error: 'this' implicitly has type 'any' because it does not have a type annotation.
}
new People() // error: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
函数调用或call、apply调用
如果通过call,apply调用,this则是动态的值,Typescript支持显式声明this的类型
const person = {
name: "name",
getNam() {
return this.name
},
}
function setName(this: typeof person, name: string) {
this.name = name
}
setName.call(person, 'new name')
如上示例,显式声明了setName函数内部this的类型