TypeScript中具有泛型的条件类型 [英] Conditional types with Generics in TypeScript

查看:98
本文介绍了TypeScript中具有泛型的条件类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要实现的目标可以用代码来最好地解释:

What I want to achieve can be best explained in code:

给出鞋类和服饰类:

class Shoe {
    constructor(public size: number){}
}

class Dress {
    constructor(public style: string){}
}

有一个仅包含Shoe或Dress的通用框.不能同时包含两者:

Have a generic box that can only contain Shoe or Dress. Can't contain both:

class Box <T extends Shoe | Dress > {
}

然后有一个实用类,可以处理运动鞋:

Then have a utility class that takes care of moving shoes:

class ShoeMover {
    constructor(public size: number[]){}
}

还有一个用于移动连衣裙的实用程序类:

Also, a utility class for moving Dresses:

class DressPacker {
    constructor(public style: string[]){}
}

然后有一个通用移动器,如果用Box<Shoe>Box<Dress>实例化,则具有使用ShoeMoverDressPackermover方法:

Then have a generic mover, that if instantiated with Box<Shoe> or Box<Dress> has a mover method that makes use of either the ShoeMover or the DressPacker:

class Move<B extends Box<Shoe> | Box<Dress>> {
    private box: B;
    constructor(toMove: B) {
        this.box = toMove;
    }
    public mover(tool: ShoeMover | DressPacker) {
    }
}

然后,编译时间保证应该是,如果用Box<Shoe>实例化Move,则mover方法应仅接受ShoeMover.如果用Box<Dress>实例化. mover方法应仅接受DressPacker.那就是:

Then the compile time guarantee should be that, if Move is instantiated with Box<Shoe>, then the mover method should only accept ShoeMover. If instantiated with Box<Dress>. the mover method should only accept DressPacker. That is:

let shoemover = new Move(new Box<Shoe>());

// compile
shoemover.mover(new ShoeMover([21]))

// should not compile. But currently does
shoemover.mover(new DressPacker(["1"]))

我尝试使用条件类型,但是我猜想涉及泛型的事实使预期的解决方案无法正常工作.基本上这是我尝试过的:

I tried using conditional types, but I guess the fact that generics is involved makes the intended solution not to work. Basically this is what I have tried:

type MoverFromEitherShoeOrDressA<T> =
    T extends Box<infer U> ?
        U extends Shoe ? ShoeMover :
        U extends Dress ? DressPacker :
        never:
    never;

and 

type MoverFromEitherShoeOrDressB<T> =
    T extends Box<Shoe> ? ShoeMover:
    T extends Box<Dress> ? DressPacker:
never;

然后从以下位置更改mover的定义:

Then changing the definition of mover from:

public mover(tool: ShoeMover | DressPacker) {
}

public mover(tool: MoverFromEitherShoeOrDressB) {
}

or 

public mover(tool: MoverFromEitherShoeOrDressA) {
}

..但是这些没有提供我所寻求的编译时间保证.

..but these did not give the compile time guarantees that I sought.

有人知道如何实现这一目标吗?

Anyone knows how to achieve this?

编辑.

可接受的答案适用于上述情况.但是有一个略有不同的方案是行不通的.我没有提出其他问题,而是决定进行更新.情况是Move的构造函数更改为采用联合类型.

The accepted answer works for the scenario above. But there is a slightly different scenario which does not work. Instead of creating another question, I decided to update. The scenario is when the constructor of the Move is changed to take in union type.

type Mover<T> = 
  T extends Shoe ? ShoeMover : 
  T extends Dress ? DressPacker : 
  never; 

class Move<T extends Shoe | Dress> {
    private box: Box<T>;
    constructor(public toMove: Box<Shoe>[] | Box<Dress>[]) {
        this.box = toMove;
    }
    public mover(tool: Mover<T>) {
    }
}


let shoemover = new Move(new Array<Box<Shoe>>());

// compile
shoemover.mover(new ShoeMover([21]))

// should not compile. But currently does
shoemover.mover(new DressPacker(["1"]))

游乐场链接

推荐答案

您快到了,您也只需要在mover方法中使用泛型,否则它将不知道T是什么.将通用类型视为将通用T用作参数,将<>用作()的方法:

You were almost there, you just need to use generics in the mover method too, elseway it will not know what T is is. See the generic type as a method which takes a generic T as a parameter, and <> as ():

type Mover<T> = 
  T extends Shoe ? ShoeMover : 
  T extends Dress ? DressPacker : 
  never; 

class Move<T extends Shoe | Dress> {
    private box: Box<T>;
    constructor(toMove: Box<T>) {
        this.box = toMove;
    }
    public mover(tool: Mover<T>) {
    }
}

此外,我更改了Move定义以排除Box泛型,因为您可以轻松地将其封装在类内部定义中,但是您的解决方案也可以使用:

Furthermore, I changed the Move definition to exclude the Box generic since you can easily encapsulate it in the class inner definitions, but your solution would work too with:

type MoverFromEitherShoeOrDressA<T> =
    T extends Box<infer U> ?
        U extends Shoe ? ShoeMover :
        U extends Dress ? DressPacker :
        never:
    never;

public mover(tool: MoverFromEitherShoeOrDressA<B>) { // <-- Here
}

加入操场这里: 查看全文

登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆