-
Notifications
You must be signed in to change notification settings - Fork 0
Description
用通俗易懂的方式说明 Typescript 中的协变与逆变。纯属个人理解,不保证内容正确性。
协变
interface Parent {
name: string
}
interface Child {
name: string
age: number
}
let p:Parent = {
name: '张三'
}
let c:Child = {
name: '李四',
age: 18
}
p = c; // √
c = p; // × Property 'age' is missing in type 'Parent' but required in type 'Child'以上定义了父类 Parent 与子类 Child,并声明了变量 p 的类型为 Parent,变量 c 的类型为 Child。可以看到把变量 c 赋值给 p 的时候通过了 ts 的类型检查, 但是把 p 赋值给 c 的却不行。 ts 的类型只关注值的结构或者外形上能兼容即可。
变量 p 的类型为 Parent,意味着只要 p 的值的外形上只要存在 name 属性就可以。所以 ts 进行检查的时候没有问题。
变量 c 的类型为 Child,意味着只要 c 的值的外形上(可以理解成代码结构上)存在 name 与 age 属性就行。所以变量 c 赋值给 p 的时候是没有问题的,因为 c 的类型结构上也存在 p 所需要的 name,也叫类型兼容。反之 p 赋值给 c 就步行。 p 的类型没有 c 的类型所需要的 age 属性。
下面是类型是函数的情况,需要在 tsconfig.json 中 的 compilerOptions 中的 strictFunctionTypes 配置打开
let parent: (a:string) => void
let child: (a:string,b:string) => void;
parent = a => {
console.log(a)
}
child = (a, b) => {
console.log(a + b)
}
parent = child // × Type '(a: string, b: string) => void' is not assignable to type '(a: string) => void'
child = parent // √ 可以看到 parent 函数与 child 函数的区别就是 child 函数多一个参数。但是把 child 函数赋值给 parent 变量的时候,ts 类型校验不通过, parent 函数赋值给 child 变量确是没有问题。原因在于 child 函数需要两个参数,child 函数赋值给 parent 变量的时候, parent 就是 child 函数本身了,但是 parent 的类型声明中函数只有一个参数,就导致了类型声明和值的参数结构上存在不兼容。同理
parent 函数赋值给 child 变量可以是因为 parent 的类型函数只有一个变量,这相当于 child 变量的值是 parent 函数本事,child 的变量的类型函数的参数是两个,能兼容 parent 函数参数,所以是可以的。可以这么理解,child 函数类型声明需要两个参数,但实际的 child 变量的值的函数只有一个参数,这种情况下就算按照 child 类型声明的方式传递两个参数,child 的值的函数只需要一个参数,能兼容,也不会执行错误,所以是可以的。
两个例子其实都是说明只要实际执行的时候你赋值的变量的类型能兼容,运行不报错那 ts 类型检查就不会出问题。
逆变
再看另一种都是只有一个参数,类型不同的情况
interface Parent {
name: string;
}
interface Child {
name: string;
age: number
}
let a:(param: Parent) => void;
let b:(param: Child) => void;
a = param => {
console.log(param.name)
}
b = param => {
console.log(param.age + param.name)
}
a = b // × Type '(param: Child) => void' is not assignable to type '(param: Parent) => void'
b = a // √ b 赋值给 a 不行是因为 b 函数赋值给 a 变量之后相当于 a 变量的值是 b 指向的函数,该函数使用到了参数中的 age 和 name 属性,但是 b 的类型参数中只有一个 age。意味着你使用到这个 a 的函数的时候会按照 a 的类型传值。这样这个 a 函数就不安全意味着可能执行报错,因为没有传递这个 age 的属性。这种现象称为逆变。
总结
逆变或者协变的本质都是保证 js 的代码正常运行, ts 只是给 js 添加了静态类型系统来保证 js 安全执行。个人感觉这种很理论的东西更让你难以理解。ts 中的类型的父与子与面向对象编程中的概念是没有关系的, ts 是用 值的形状来区分父与子,以就是说只要一个类型能兼容另一个类型,那它就是子类型。