Guilherme Baron
Guilherme Baron

Reputation: 43

How to define a type for a function like this?

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?

Playground: http://www.typescriptlang.org/play/index.html?ssl=1&ssc=1&pln=10&pc=1#code/C4TwDgpgBASg9gV2NAvFA3gKCjqAKASgC4oBnYAJwEsA7Ac21wG0BrCEE86+gXRPiQRMAX0yYAxnBrkoFOHGD9EyKGkKqAfFADkchdsx7gAOnEALKgBsAJqvwFNUAAYASdEcLCA9OavWnhvImvjbGdBQAhjTWIbZqDiharu5BphY2nl7hUTHp-mJAA

Upvotes: 4

Views: 83

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

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

Play

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()

Play

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`)
    })
})

Play

Upvotes: 3

Related Questions