TypeScript中的泛型条件类型

17
我可以帮您进行翻译。这段内容是关于编程的,讲述了如何定义一个鞋子类和一个裙子类。
class Shoe {
    constructor(public size: number){}
}

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

有一个通用的盒子,只能装鞋子或裙子。不能同时装两者:

class Box <T extends Shoe | Dress > {
}

然后有一个实用类来处理鞋子的移动:
class ShoeMover {
    constructor(public size: number[]){}
}

还有一个用于移动 Dresses 的实用类:

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

然后有一个通用的移动器,如果用 Box<Shoe>Box<Dress> 实例化,就会有一个 mover 方法,该方法利用 ShoeMoverDressPacker 中的一个。
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。也就是说:
let shoemover = new Move(new Box<Shoe>());

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

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

我尝试使用条件类型,但是我猜测泛型的参与使得预期解决方案无法工作。基本上这就是我尝试过的:

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 的定义从以下内容更改为:

public mover(tool: ShoeMover | DressPacker) {
}

public mover(tool: MoverFromEitherShoeOrDressB) {
}

or 

public mover(tool: MoverFromEitherShoeOrDressA) {
}

...但是它们没有给我所需要的编译时保证。

有人知道如何实现这个?

编辑。

接受的答案适用于上述情况。但是存在一个略微不同的情况,该情况无法工作。我决定更新而不是创建另一个问题。该情况是当Move的构造函数更改为接受联合类型时。

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"]))

{{link1:游乐场链接}}

1个回答

14
你已经接近成功了,你只需要在mover方法中也使用泛型,否则它将不知道T是什么。把泛型类型看作一个以泛型T为参数的方法,<>就像()一样:
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泛型排除在外,因为你可以很容易地将其封装在类内部定义中,但你的解决方案也可以使用以下代码实现:
type MoverFromEitherShoeOrDressA<T> =
    T extends Box<infer U> ?
        U extends Shoe ? ShoeMover :
        U extends Dress ? DressPacker :
        never:
    never;

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

编辑:在这里添加了playground:Playground链接


一如既往,我没有耐心去重现我所遇到的确切问题。确切的情况涉及到 move 构造函数像这样:constructor(public toMove: Box<Shoe>[] | Box<Dress>[]) // 因为 move 可以移动多个鞋盒或衣服盒的盒子。 - Finlay Weber
@FinlayWeber,在你的问题中添加附加信息,而不是回答。 - Dale K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接