For this example, I aim to specify the return type of a function:
type MakeNamespace<T> = T & { [index: string]: T }
export interface INodeGroupProps<T = unknown[], State = {}> {
data: T[];
start: (data: T, index: number) => MakeNamespace<State>;
}
export interface NodesState {
top: number;
left: number;
opacity: number;
}
export type Point = { x: number, y: number }
const points: Point[] = [{ x: 0, y: 0 }, { x: 1, y: 2 }, { x: 2, y: 3 }]
const Nodes: INodeGroupProps<Point, NodesState> = {
data: points,
keyAccessor: d => {
return d.x
},
start: point => {
return {
top: point.y,
left: point.x,
opacity: 0,
}
}
}
The potential return types for the start
function are as follows:
return {
top: point.y,
left: point.x,
opacity: 0
}
Or it could also be:
return {
namespace1: {
top: point.y,
left: point.x,
opacity: 0
},
namespace2: {
top: point.y,
left: point.x,
opacity: 0
}
}
An issue arises when TypeScript complains:
Property 'top' is incompatible with index signature
Modifying MakeNamespace
to
type MakeNamespace<T> = T | { [index: string]: T }
solves the issue but doesn't handle cases like this:
const Nodes: INodeGroupProps<Point, NodesState> = {
data: points,
start: point => {
return {
top: point.y,
left: point.x,
opacity: 0
namespace1: {
top: point.y,
left: point.x,
opacity: 0
}
}
}
}
In this case, where there is a mix of both.
Due to type widening in returns, the namespace
key loses type safety by defaulting to the T
section of the union.
A possible solution might involve making the indexer optional, though the approach is unclear at the moment.
You can experiment further on a playground.