简体   繁体   English

如何递归使用Typescript泛型?

[英]How to use Typescript generics recursively?

I am having an issue using recursive generics. 我在使用递归泛型时遇到问题。

I created an interface using generics. 我使用泛型创建了一个接口。 The idea is a branch can have many child branches which are also generics. 这个想法是一个分支可以有很多子分支,它们也是泛型的。 This is my code ( I cut it down for posting) 这是我的代码(我将其删除以进行发布)

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

The issue is that when I construct an Office branch, the children array is being constructed as Office type. 问题是,当我构造一个Office分支时,children数组被构造为Office类型。

This means when I try to assign an array of Contact branches as the children of the Office branch typescript throws the following error; 这意味着当我尝试分配一个Contact分支数组时,因为Office分支打字稿的子项会引发以下错误;

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

The reason for doing this is because the type of branches are unknown and I don't really want to use any either if I can avoid it. 这样做的原因是因为分支的类型是未知的,如果可以避免的话,我也不想使用any一种。

So, How would I resolve this? 那么,我该如何解决呢?

It seems like you may be using Generics where simple inheritance would do. 似乎您可能在使用泛型来实现简单继承。 If all you need is for an office or a contact branch to have a list of branch children, then the following code works using only a generic Array. 如果您只需要办公室或联系人分支具有分支子列表,则以下代码仅使用通用Array起作用。

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());

However if you are intent on using generics, you need 2 generic types in your Branch class to decouple the record and children types, like such: 但是,如果您打算使用泛型,则需要在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>();

So, if you really want to strongly type Branch , you need to give it a type corresponding to all the nested levels of children . 因此,如果您真的想强类型化Branch ,则需要为其赋予一个与所有children级嵌套子级相对应的类型。 That would look like a list or tuple of types. 这看起来像是类型的列表或元组。 Since TypeScript 3.0 introduced tuple types in rest/spread expressions , you can kind of express this, but I don't know if it's worth it to you. 由于TypeScript 3.0 在rest / spread表达式中引入了元组类型 ,因此您可以表达这一点,但是我不知道这是否值得。

First, let's define the type functions Head and Tail which split a tuple type into its first element and a tuple of the rest of the elements: 首先,让我们定义类型函数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]

Now we can define BranchInterface or Branch to take a tuple of types like this: 现在我们可以定义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>) { }
}

Assuming you know that you want the top level to be an Office and the next level down to be a Contact , then you can define your list of types as [Office, Contact] and see if it works: 假设您希望将顶层作为Office ,将下一层作为Contact ,则可以将类型列表定义为[Office, Contact]然后查看它是否有效:

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

Of course if you traverse past that, you find out what Head<[]> is (that implementation gives {} , I guess): 当然,如果您经过那一步,就会发现Head<[]>是什么(该实现给出{} ,我猜):

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

If you want to make the layers below Contact be something like never instead (because you will never traverse down that far), you can use a rest tuple like this: 如果您想使Contact下面的图层变得像never没有(因为您永远不会遍历那么远),可以使用如下这样的rest元组

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

Note that this requires you to explicitly specify the type parameter T when constructing a Branch , since the compiler cannot infer the type from the argument: 请注意,这要求您在构造Branch时显式指定类型参数T ,因为编译器无法从参数推断类型:

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

Well, it works. 好吧,它有效。 Up to you if you want to go that way. 如果您想那样做,则取决于您。 Hope that helps; 希望能有所帮助; good luck! 祝好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM