如何递归使用Typescript泛型?

时间:2018-08-29 14:01:43

标签: typescript

我在使用递归泛型时遇到问题。

我使用泛型创建了一个接口。这个想法是一个分支可以有很多子分支,它们也是泛型的。这是我的代码(我将其删除以进行发布)

import Contact from 'Business/Models/Contact';
import Office from 'Business/Models/Office';

export interface BranchInterface<T> {
    children: Array<BranchInterface<T>>;
    record: T;
}

export class Branch<T> implements BranchInterface<T> {

    public children: Array<BranchInterface<T>> = [];

    constructor(public record: T) {}
}


const newOffice = new Office();

const rootBranch = new Branch<Office>(newOffice);

rootBranch.children = Array<Branch<Contact>>(); << is the issue

问题是当我构造一个Office分支时,子数组被构造为Office类型。

这意味着当我尝试分配Contact分支的数组时,Office分支打字稿的子项会引发以下错误;

Type 'Branch<Contact>[]' is not assignable to type 'Branch<Office>[]'...

之所以这样做,是因为分支的类型是未知的,如果可以避免的话,我也不想使用any

那么,我该如何解决?

2 个答案:

答案 0 :(得分:0)

似乎您可能在使用泛型来实现简单继承。如果您只需要办公室或联系人分支具有分支孩子的列表,则以下代码仅适用于通用数组。

export class Branch {
  children: Array<Branch>;
  constructor(){
    this.children = new Array<Branch>();
  }
}

class Office extends Branch {}
class Contact extends Branch {}

const newOffice = new Office();
newOffice.children.push(new Contact());

但是,如果您打算使用泛型,则需要在Branch类中使用2个泛型类型来分离记录和子类型,例如:

export class Branch<T, U> {

  public children: U[];

  constructor(public record: T) {
    this.children = [];
  }
}

class Office {};
class Contact {};

const newOffice = new Office();

const rootBranch = new Branch<Office, Contact>(newOffice);

rootBranch.children = new Array<Contact>();

答案 1 :(得分:0)

因此,如果您真的想强行键入Branch,则需要为其提供一种与children的所有嵌套级别相对应的类型。这看起来像是类型的列表或元组。由于TypeScript 3.0引入了tuple types in rest/spread expressions,因此您可以表达这一点,但是我不知道这是否值得。

首先,让我们定义类型函数HeadTail,它们将元组类型分为其第一个元素和其余元素的元组:

type HeadTail<L extends any[]> = 
  ((...args: L) => void) extends ((x: infer H, ...args: infer T) => void) ? [H,T] : never
type Head<L extends any[]> = HeadTail<L>[0];
// e.g., Head<[string, number, boolean]> is string
type Tail<L extends any[]> = HeadTail<L>[1]; 
// e.g., Tail<[string, number, boolean]> is [number, boolean]

现在,我们可以定义BranchInterfaceBranch来接受如下类型的元组:

export interface BranchInterface<T extends any[]> {
  children: Array<BranchInterface<Tail<T>>>
  record: Head<T>;
}

export class Branch<T extends any[]> {
  public children: Array<BranchInterface<Tail<T>>> = [];
  constructor(public record: Head<T>) { }
}

假设您希望将顶层设为Office,将下一层设为Contact,则可以将类型列表定义为[Office, Contact]并查看如果可行:

const rootBranch = new Branch<[Office, Contact]>(newOffice);
const anOffice = rootBranch.record; // Office
const aContact = rootBranch.children[0].record; // Contact

当然,如果您经过这一点,就会发现Head<[]>是什么(我想该实现会提供{}):

const whoKnows = rootBranch.children[0].children[0].record; // {}

如果要将Contact下面的图层改成never(因为您将永远不会向下走那么远),则可以使用rest tuple,如下所示:

const rootBranch = new Branch<[Office, Contact, ...never[]]>(newOffice);
const anOffice = rootBranch.record; // Office
const aContact = rootBranch.children[0].record; // Contact
const aNever = rootBranch.children[0].children[0].record; // never
const anotherNever = rootBranch.children[0].children[0].children[0].record; // never

请注意,这需要您在构造T时显式指定类型参数Branch,因为编译器无法从参数推断类型:

const oops = new Branch(newOffice); 
oops.record; // any, not Office

嗯,它有效。如果您想那样做,则取决于您。希望能有所帮助;祝你好运!