I have a function that generates an array of objects called nodes:
type Node = {
type: 'text' | 'entity';
// A node of type 'entity' for sure has props defined while a 'text' node does not have them.
// I also require those nodes to be in the same array when returned from the buildNodes function
props?: EntitySpecificProps;
}
type EntitySpecificProps = {
id: string;
}
function buildNodes(): Node[] {
...
return nodes;
}
Now let's say I generate some nodes and pass them to another function which is defined as:
function useEntityNode(props: EntitySpecificProps){
...
}
const nodes = buildNodes();
// typescript gives error because node props may be possibly undefined (given the optional type of 'props').
nodes.map((node) => node.type === 'text' ? node : useEntityNode(node.props))
How can I constrain node props to be defined if the type field of a node is of type entity
?
CodePudding user response:
You can make this work by removing the Optional "?"
from props when type is "entity"
by using this NodeUnion
mapping transformation.
NodeUnion
makes sure that if type = "text"
we get { type: "text", props ?: T }
If type = "entity"
we get { type: "entity", props: T }
ie Props field becomes required
export type Node = {
type: 'text' | 'entity';
props?: EntitySpecificProps;
}
/* This makes sure that if type = 'text' we get { type: 'text', props ?: T }
if type = 'entity' we get { type: 'entity', props: T } ie Props field becomes required
*/
export type NodeUnion =
| Node["type"] extends infer T
? T extends any
? T extends 'entity'
? Required<Node> & { type: T }
: Node & { type: T }
: never
: never
export type EntitySpecificProps = {
id: string;
}
declare function buildNodes(): NodeUnion[]
declare function useEntityNode(props: EntitySpecificProps): void
const nodes = buildNodes();
nodes.map((node) => node.type === 'text' ? node : useEntityNode(node.props))
CodePudding user response:
You could specify all possible Node
sub-types in their separate type
. The types can then be discriminated by their type
property.
type _Text = {
type: 'text'
}
type Entity = {
type: 'entity'
props: EntitySpecificProps
}
type EntitySpecificProps = {
id: string;
}
type _Nodes = _Text | Entity
function buildNodes(): _Nodes[] {
return [];
}
function useEntityNode(props: EntitySpecificProps){}
const nodes = buildNodes();
nodes.map((node) => node.type === 'text' ? node : useEntityNode(node.props))
Note: I prefixed some types here with an _
because the names Text
and Node
collide with existing types.