Reputation: 761
Suppose we have the interface:
interface Node<C extends Node[] = any[]> {
children: C
}
Here, C is a generic that's a tuple that is the type of this node's children.
Let's define some nodes:
type Foo = Node<[]>
type Bar = Node<[Foo, Foo]>
type Baz = Node<[Bar]>
Baz is the root node. It is the parent of one Bar node, which is the parent of two Foo nodes. Foo has no children.
If I want to get the children of a node, I can do:
type TupleOfNodeChildren<N extends Node> = N['children'];
Here are some examples of this TupleOfNodeChildren
type, which works as expected:
type T0 = TupleOfNodeChildren<Foo> // []
type T1 = TupleOfNodeChildren<Bar> // [Foo, Foo]
type T3 = TupleOfNodeChildren<Baz> // [Bar]
Now let's say that I want a type that is a union of every different type in the tuple. I can do:
type TypesOfNodeChildren<N extends Node> = TupleOfNodeChildren<N>[number];
And then of course our examples:
type T10 = TypesOfNodeChildren<Foo> // never
type T11 = TypesOfNodeChildren<Bar> // Foo
type T12 = TypesOfNodeChildren<Baz> // Bar
All of that works nice and fine. But what if I want something called TypesOfAllChildren
, which is like TypesOfNodeChildren
, but instead of just being a union of the immediate children, it is a union of all of the node's children?
This is how it would work:
type T20 = TypesOfAllChildren<Foo> // never
type T21 = TypesOfAllChildren<Bar> // Foo
type T22 = TypesOfAllChildren<Baz> // Bar | Foo <--- Includes types of deep children
Notice that T22
has both Bar, the immediate child of Baz, and then also Foo, which is the child of Bar.
I can't seem to get this TypesOfAllChildren
type to work; it keeps complaining about a circular reference no matter what I try. I am assuming that you need some kind of recursion to get the types of all of the children, but I am not sure how to implement that without TypeScript complaining. Here is a playground with these types and examples.
EDIT:
Here is an example of what I tried:
type TypesOfAllChildren<N extends Node> = TypesOfNodeChildren<N> | TypesOfAllChildren<TypesOfNodeChildren<N>>;
// ~~~~~~~~~~~~~~~~~~ Recursively references itself
Adding an exit condition via a conditional type also doesn't work:
type TypesOfAllChildren<N extends Node> = TypesOfNodeChildren<N> | (TypesOfNodeChildren<N> extends never ? never : TypesOfAllChildren<TypesOfNodeChildren<N>>);
Upvotes: 4
Views: 1021
Reputation: 761
I solved it. So we have:
interface Node<C extends Node[] = any[]> {
children: C
}
type Foo = Node<[]>
type Bar = Node<[Foo, Foo]>
type Baz = Node<[Bar]>
type Faz = Node<[Baz]>
And want to have a type that can get a union of all children, everywhere in the tree. I found that this works:
type SelfAndAllChildren<N extends Node> = N['children'][number] extends never ? N : {
// @ts-ignore
getSelfAndChildren: N | SelfAndAllChildren<N['children'][number]>
}[N extends never ? never : 'getSelfAndChildren']
Okay, so first:
never
, I.E. the node doesn't have any children. If that is the case, then we just return N
and are done.getSelfAndChildren
. We need to do this because only the types of properties are allowed to recursively reference themselves -- union types can't.getSelfAndChildren
property will include the node itself, N
, and then the SelfAndAllChildren
of the node's children. This is a recursive reference to SelfAndAllChildren
, but because it's on the getSelfAndChildren
property, TypeScript doesn't complain.getSelfAndChildren
property. Unfortunately, TypeScript will complain about a recursive reference unless we use a conditional type to index the interface with getSelfAndChildren
in it. So, we put in a conditional which will always return the type of getSelfAndChildren
-- which itself has a recursive reference to SelfAndAllChildren
.@ts-ignore
above the getSelfAndChildren
. If we were to pass in any
to SelfAndAllChildren
, that would throw an error. Putting the @ts-ignore
silences the error on any
, and the type defaults to any
, which is what we want.Please note that this is "not recommended" because of its possible performance impact.
Also, here is a type that just gets the children in case you were looking for that:
type JustAllChildren<N extends Node> = SelfAndAllChildren<N['children'][number]>
And then here is a playground with everything.
Upvotes: 1