Reputation: 43
I'm moving some code to TS and I've been struggling to define a type for a group of routing functions. That's how they look like:
const root: Route = () => 'root'
root.child = () => `${root()}/child`
root.child.grandchild = () => `${root.child()}/grandchild`
I've tried to define some kind of recursive callable type or interface alongside an index signature for the nested attributes, but without much success:
type Route = {
(): string
[key: string]: Route
}
Any thoughts or ideas on how I can do that?
Upvotes: 4
Views: 83
Reputation: 249636
Depending on what you are trying to do there are several options.
If you use a regular function
definition you can just add extra properties to the function in the scope where the function was declared and TS will recognize these as properties on the function:
function root() { return 'root' }
function child() { return `${root()}/child` }
child.grandchild = () => `${root.child()}/grandchild`
root.child = child;
root.child.grandchild() //ok
Another option is to use Object.assign
to create the function with properties in one go:
const root = Object.assign(() => 'root', {
child: Object.assign(() => `${root()}/child`, {
grandchild: () => `${root.child()}/grandchild`
})
});
root.child.grandchild()
Neither of these options actually uses your Router
interface which allows any string property to be later added. To do this, I think the simplest option is to create a helper function that will internally use Object.assign
but that will also use the desired types:
type Route = {
(): string
[key: string]: Route
}
function createRoute(fn: () => string, other: Record<string, Route> = {}) {
return Object.assign(fn, other);
}
const root: Route = createRoute(() => 'root', {
child: createRoute(() => `${root()}/child`, {
grandchild: createRoute(() => `${root.child()}/grandchild`)
})
})
Upvotes: 3