[英]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: 首先,让我们定义类型函数Head
和Tail
,将元组类型分为其第一个元素和其余元素的元组:
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: 现在我们可以定义BranchInterface
或Branch
以采用如下类型的元组:
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.