Skip to content

Latest commit

 

History

History
110 lines (84 loc) · 4.57 KB

简单理解逆变和协变.md

File metadata and controls

110 lines (84 loc) · 4.57 KB
name title tags categories info oldTime time desc keywords
简单理解逆变和协变
简单理解逆变和协变
技术
瞎折腾
瞎折腾
简单的简单理解
2023/1/29
2023/12/29
关键在于父子类型的赋值
typescript
逆变
协变

简单理解逆变和协变

参考资料:

PS: Ts 在函数参数的比较中实际上默认采取的策略是双向协变:只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。可以通过tsconfig.json中修改strictFunctionType属性来严格控制协变和逆变。

Demo链接

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'});

2023-12-29 更新

实践:使用泛型 + 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])