【typescript】ts协变逆变双向协变高级用法

前言

  • 本篇主要说一下协变逆变与双向协变的概念与高级用法
  • 对于部分干货文章,决定采用粉丝可见,反正也是白嫖不花钱。

协变逆变

  • 以前初学ts感觉协变与逆变是个很复杂的东西,最近重新复习了下发现尼玛,其实就是参数可以接受比其定义的类型更宽广的类型,而返回值可以接受比其定义的类型更狭窄的类型。这里狭窄和宽广意思就是比限定的属性多还是少。比如我们做组件库时,参数会限定个比各种可能都宽广的类型,最宽广的类型自然是any了。而函数的返回值则会比限定更狭窄类型,返回值情况用的较少,一般都是明确返回值类型与其相等。
  • 可以看几个例子:
class Animal {
      }
class Cat extends Animal {
     
    meow() {
     
        console.log('cat meow');
    }
}
class Dog extends Animal {
     
    wow() {
     
        console.log('dog wow');
    }
}
function test1(v:Cat):Cat {
     
    return v
}
function test2(v:Animal):Animal {
     
    return v
}
let a  = test1(Animal)//报错
let b = test2(Cat)//不报错
  • 这里test1要的cat是更狭窄的类型,而Animal比cat类型要大,所以tes1传递animal会报错,但是test2传递cat就不报错,b的类型推断为animal 。此时如果给b类型定义let b:Cat = test2(Cat)则报错。
  • 这种函数并不是很方便看返回值类型,我们可以直接定义个参数来看类型:
class Animal {
      }

class Cat extends Animal {
     
    meow() {
     
        console.log('cat meow');
    }
}
class Dog extends Animal {
     
    wow() {
     
        console.log('dog wow');
    }
}
class SmallDog extends Dog{
     
    public name :string ='yehuozhili'
}
//参数dog,返回值Dog
type testType = (v:Dog)=>Dog
function exec(v:testType){
     
    v(new SmallDog)
}
//试验:
type childToChild = (v:SmallDog)=>SmallDog
let aaaa :childToChild = (v)=>new SmallDog
exec(aaaa)
type childToParent = (v:SmallDog)=>Animal
let aaaa2 :childToParent=(v)=>new Animal
exec(aaaa2)
type parentToChild = (v:Animal)=>SmallDog
let aaaa3:parentToChild =(v)=>new SmallDog
exec(aaaa3)//不报错
type parentToParent =(v:Animal)=>Animal
let aaaa4:parentToParent=(v)=>new Animal
exec(aaaa4)
  • 其中,只有parentToChild不报错,其余全部报错,说明返回值可以是兼容比定义更狭窄的类型。
  • tsconfig配置里有个 “strictFunctionTypes”: true,这个玩意是允许参数进行双向协变,什么是双向协变?就是参数既可以协变又可以逆变呗。还是上面那个例子,当我们关闭这个选项:
let aaaa :childToChild = (v)=>new SmallDog
exec(aaaa)//不报错
type childToParent = (v:SmallDog)=>Animal
let aaaa2 :childToParent=(v)=>new Animal
exec(aaaa2)
type parentToChild = (v:Animal)=>SmallDog
let aaaa3:parentToChild =(v)=>new SmallDog
exec(aaaa3)//不报错
type parentToParent =(v:Animal)=>Animal
let aaaa4:parentToParent=(v)=>new Animal
exec(aaaa4)
  • 可以发现childToChild也不报错了,当返回值是一个更狭窄类型时(满足返回值要求),参数可以进行双向协变,就是参数既可以是父类也可以是子类,这2种都不会报错。
  • 协变与逆变更高级的用法是利用其特性来完成类型转化,比如把元组类型转化为字面量类型就用到了协变:
type tuple = ['123','aaa','dff']
type change = tuple extends Array<infer F> ?F:never
  • 其中,F为什么是字面量类型而非string类型?因为参数协变需要个比定义更狭窄的类型,而非更宽广的类型,否则F为any不是也符合条件?这就是利用协变的高级用法!
  • 再举个例子:
type T1 = {
      name: string };
type T2 = {
      age: number };

type UnionToIntersection<T> = T extends {
      a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
type T3 = UnionToIntersection<{
      a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2
  • 其中,t3为t1和t2交叉类型,为什么是交叉类型而不是联合类型?这就是参数协变。
  • 我们把这个例子修改下:
type T1 = {
      name: string };
type T2 = {
      age: number };

type UnionToIntersection<T> = T extends {
      a: (x:T1 ) => infer U; b: (x: T2) => infer U } ? U : never;
type T3 = UnionToIntersection<{
      a: (x: T1) => string; b: (x: T2) => void }>; // string|void
  • 此时,T3变为string|void类型,为啥是联合类型而不是交叉类型?这就是返回值逆变。

你可能感兴趣的:(typescript)