Consider the following function rootFunction. We want to extend the
functionality of the main function by adding a number of possible
"subfunctions". These subfunctions are declared at compile time. This means
TypeScript should be able to give us type information and suggestions; for
example rootFunction.b
when typing rootFunction.
.
// Declaration
const keyArray = ["b", "c"];
// We want to be able to call rootFunction()
function rootFunction() {
return "123";
}
// we want to be able to call rootFunction.<key>() where <key> is a key of keyArray.
keyArray.forEach((key) => {
rootFunction[key] = () => {
return key + rootFunction();
};
});
// Usage
rootFunction(); // 123
rootFunction.b(); // b123
rootFunction.c(); // c123
My initial attempt is documented below. The problem here is the RootFunction Interface; it doesn't allow us to use the keyArray to declare the different sub-functions.
/**
* If we mark keyArray as const, it will become type `readonly ["b", "c"]` instead of
* string[]. This means DynamicArrayKeys will be of type `"b" | "c"`.
* Thus, we can use it as type information for the RootFunction extensions
*/
const keyArray = ["b", "c"];
type DynamicArrayKeys = (typeof keyArray)[number];
interface RootFunctionBase {
(): string;
}
/**
* THE FOLLOWING INTERNFACE WILL NOT WORK! The following error occurs. The problem is that we are using an interface, but more on that later.
* A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type. ts(1169)
* A computed property name must be of type 'string', 'number', 'symbol', or 'any'. ts(2464)
*/
// interface RootFunction extends RootFunctionBase {
// [key in DynamicArrayKeys]: RootFunctionBase;
// }
// This is a naive solution; by declaring everything manually, we get it to work.
interface RootFunction extends RootFunctionBase {
b: RootFunctionBase;
c: RootFunctionBase;
}
let rootFunction = function () {
return "123";
} as RootFunction;
keyArray.forEach((key) => {
rootFunction[key] = () => {
return key + rootFunction();
};
});
// usage
const a = rootFunction(); // works.
const b = rootFunction.b(); // also works.
Instead of using an interface, simply using a type will solve the issue. Thanks Gerrit0 for helping me with this.
/**
* If we mark keyArray as const, it will become type `readonly ["b", "c"]` instead of
* string[]. This means DynamicArrayKeys will be of type `"b" | "c"`.
* Thus, we can use it as type information for the RootFunction extensions
*/
const keyArray = ["b", "c"] as const;
type DynamicArrayKeys = (typeof keyArray)[number];
type RootFunctionBase = () => string;
/**
* Using a Type will solve the issue.
*/
type RootFunction = RootFunctionBase & {
[key in DynamicArrayKeys]: RootFunctionBase;
};
let rootFunction = function () {
return "123";
} as RootFunction;
keyArray.forEach((key) => {
rootFunction[key] = () => {
return key + rootFunction();
};
});
// TESTING
const a = rootFunction(); // works.
const b = rootFunction.b(); // also works.
const c = rootFunction.c(); // also works.