In typescript, I want to declare an extensible interface via an indexer, to allow additional functions to be associated dynamically, but have sub properties that refer back to those indexed functions by name. Something tells me this may be beyond the acrobatics the TS compiler is capable of, but I've rarely been able to stump it. Any ideas?
interface Foo {
someProp: {
[x: string]: keyof this; // doesn't work... but something like this to refer to indexed keys?
};
[x: string]: Function; // client can provide whatever functions they want in here
}
// usage
let myFoo: Foo = {
bar: {
validFuncName: "someFooFunc", // should work
invalidFuncName: "thisAintNoFunc" // should error!
},
someFooFunc: () => {}
}
CodePudding user response:
Based on @kelly's answer, here is a slightly less "over engineered" solution.
function helper<T extends Record<string, any>>(obj: {
[K in keyof T]: K extends "bar"
? Record<string, Exclude<keyof T, "bar">>
: Function
}){}
helper({
bar: {
validFuncName: "someFooFunc", // ok
validFuncName2: "someOtherFunc", // ok
invalidFuncName: "thisAintNoFunc" // Error
},
someFooFunc: () => {},
someOtherFunc: () => {}
})
CodePudding user response:
This is a variant of Tobias S.'s answer (and Kelly's original snippet) that maintains the type of the object passed to the helper function.
type ExtractFunctions<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K]
}
function helper<
T extends {
[K in keyof T]: K extends "bar"
? Record<string, keyof ExtractFunctions<T>>
: T[K]
}
>(obj: T): T {
return obj;
}
helper({
bar: {
validFuncName: "someFooFunc", // ok
validFuncName2: "someOtherFunc", // ok
invalidFuncName: "thisAintNoFunc" // Error
},
someFooFunc: () => {},
someOtherFunc: () => {}
});