1

Suppose I have the following JS code:

export const giveMeFunctions = (namePrefix, number) => {
    const functionA = (x) => number + x;
    const functionB = (x, y) => number * x - y;
    
    return {
        [`${namePrefix}Add`]: functionA,
        [`${namePrefix}MultSub`]: functionB,
    };
}

// This is handy because I can then do:
const {piAdd, piMultSub} = giveMeFunctions('pi', Math.PI);
const {eAdd, eMultSub} = giveMeFunctions('e', Math.E);
// No confusion between variable names, and object interpolation makes it easy to use

I cannot, however, think of a good way to type the return value of this function in Typescript. As you can see, the two keys have different types, so I can't simply do something like

const giveMeFunctions<N extends string> = (number: number): {
    [key: string]: Function;
} => {...}

as this would allow accidental interchanging of the arguments of functionA and functionB. It seems logical to me that this should be possible with Typescript, as it's all possible at compile-time. For instance, if we simply took out the concatenation step, this would be easily possible:

export const giveMeFunctions<T extends string> = (number: number): Record<T, string> => {...}

Something I expected to work would be:

const giveMeFunctions<N extends string> = (number: number): {
    [N + 'Add']: string,
    [N + 'MultSub']: string,
} => {...}

or even:

const giveMeFunctions<N extends string> = (namePrefix: N, number: number): {
    [N + 'Add']: string,
    [N + 'MultSub']: string,
} => {...}

but both of these complain that:

TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.

Is there any way to achieve the desired effect of an object type with keys defined by a concatenation of a user-specified string and a hard-coded suffix?

1
  • copied your function in vscode and hovered over the function name, gives this: { [x: string]: ((x: any) => any) | ((x: any, y: any) => number); }. You should type namePrefix as string, change number to value and make the type number, and type your arrow funcitonA,B arguments as number and the return type will then be [x: string]: (x: number, y: number) => number; Commented Jul 27, 2021 at 14:11

2 Answers 2

3

You can do smth like that:

const record = <
  Prop extends PropertyKey,
  Value extends (...args: any[]) => any
>(prop: Prop, value: Value) =>
  ({ [prop]: value }) as Record<Prop, Value>

export const giveMeFunctions = <
  T extends string,
  N extends number
>(namePrefix: T, number: N) =>
({
  ...record(`${namePrefix}Add`, (x: N) => number + x,),
  ...record(`${namePrefix}MultSub`, (x: N, y: N) => number * x - y),
})

const result = giveMeFunctions('pi', Math.PI);
result.piAdd // (x: number) => number
result.piMultSub // (x: number, y: number) => number

Playground

record function does the trick. Object with computed proeprties like {[prop]:key} is resolved as {[props:string]:any type} by the default.

As you might have noticed, I used type casting as Record<Prop, Value> to avoid this behavior

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! I hadn't thought to rely on Typescript itself to interpolate prop types like that - this is super clever.
1

If i understood correctly, you want to do something like this

type keyAdd = `${string}Add`
type keyMultSub = `${string}MultSub`

Guess it should work

const giveMeFunctions = (number: number): {
    [keyAdd]: Function,
    [keyMultSub]: Function
} => {/*some function*/}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.