name | title | tags | categories | info | oldTime | time | desc | keywords | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
简单理解逆变和协变 |
简单理解逆变和协变 |
|
瞎折腾 |
简单的简单理解 |
2023/1/29 |
2023/12/29 |
关键在于父子类型的赋值 |
|
参考资料:
PS: Ts 在函数参数的比较中实际上默认采取的策略是双向协变:只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。可以通过
tsconfig.json
中修改strictFunctionType
属性来严格控制协变和逆变。
class Animal {
public name: string = 'animal'
constructor (name: string = 'animal') {
this.name = name
}
public getName() {
return this.name
}
}
class Cat extends Animal {
public miao() {
console.log(this.name, 'said: miao miao')
}
}
let parentInstance: Animal = new Animal()
let parentFn: (arg0: Animal) => void = (arg0: Animal) => {}
let childInstance: Cat = new Cat()
let childFn: (arg0: Cat) => void = (arg0: Cat) => { arg0.miao() }
parentInstance = childInstance // ok
childFn = parentFn // ok
childInstance = parentInstance // error
parentFn = childFn // error
个人理解:无论是逆变还是协变,永远都是【更具体的类型】可以赋值给【更抽象的类型】,而不能相反。
即:
更抽象的类型<位置A> = 更具体的类型<位置B>
一般来说,父类型总会是【更抽象的类型】,子类型是【更具体的类型】,这个时候的赋值,更具体的类型处于位置 B,更抽象的类型处于位置 A。所以对于明确了类型的变量来说,都可以把子类型赋值给父类型。也就是协变。
但是对于参数是一个函数的情况时(也就是传入回调函数时),这个情况下相当于函数之间的赋值。当这个作为参数的函数,其参数是【更具体的类型】时,意味着该函数中虽然可能用到了这个【具体类型】参数中的某些特定方法,但也一定会有其父类型的方法,所以在这个时候,这个参数是【更具体的类型】的回调函数,成为了【更抽象的类型】。同理可得,参数是【更抽象的类型】的回调函数,反而会成为【更具体的类型】。也就是俗称的逆变。
其反例如下:
let animal: AnimalFn = (arg: Animal) => {} let dog: DogFn = (arg: Dog) => { arg.break(); } // 假设类型安全可以赋值 animal = dog; // 那么 animal 在调用时约束的参数,缺少 dog 所需的参数,此时会导致错误 animal({name: 'cat'});
实践:使用泛型 + never类型 适配逆变函数回调,用于取代any,且更安全
const theCallBack = (...args: string[]) => {
console.log(...args)
}
const theCallBack2 = (...args: number[]) => {
console.log(...args)
}
const useCallback = <T extends (...args: never[]) => unknown>(fn: T, args: Parameters<T>) => {
console.log('useCallback before!')
fn(...args)
console.log('useCallback after!')
}
// const useCallback = <T extends (...args: any[]) => unknown>(fn: T, args: Parameters<T>) => {
// console.log('useCallback before!')
// fn(...args)
// console.log('useCallback after!')
// }
useCallback(theCallBack, ['sss', 'wowwo'])
useCallback(theCallBack2, [123, 345])