If you need to specify the desired type, you can do so using the following syntax:
type RenderItems<T extends Record<keyof T, readonly any[]>> = {
[K in keyof T]: {
label: string;
options: XForm<T[K]>
}
}
type XForm<T extends readonly any[]> =
{ [I in keyof T]: { defaultValue: T[I], item: (value: T[I]) => void } }
const func: <const T extends Record<keyof T, readonly any[]>>(
renderItems: RenderItems<T>
) => void = (renderItems) => { }
The XForm<T>
utility type utilizes an array type argument for T
and employs a mapped type over tuple/arrays to iterate over the numeric indices I
, mapping each element individually. This way, I
acts as the equivalent of your current_index_of_array
, resulting in a processed array without needing additional brackets []
.
However, this approach may not serve your specific intent. TypeScript lacks the capability to automatically infer parameter types for context-sensitive callback functions nested within a generic array/object structure like this:
func({
prop1: {
label: "example",
options: [
{
defaultValue: "example string",
item(value) {
// ^?(parameter) value: any 😢
}
},
{
defaultValue: new Date(),
item(value) {
// ^?(parameter) value: any 😢
}
}
]
},
prop2: {
label: "example 2",
options: [
{
defaultValue: 1234,
item(value) {
//^? (parameter) value: any 😢
}
},
]
},
});
While potential solutions are being discussed, such as microsoft/TypeScript#53018, it is unlikely that this behavior will change soon. Therefore, this method might not yield the expected results at present.
If achieving inference for those callback parameters is essential, it appears to be more of an XY problem. Defining RenderItems<T>
alone won't suffice. A different strategy involves breaking down the task into smaller segments for better compiler understanding. For instance, individual elements within options
could result from function outputs with the sole purpose of validating a single pair of defaultValue
and item
:
interface Opt<T> {
defaultValue: T,
item(value: T): void;
}
function opts<T>(defaultValue: T, item: (x: T) => void): Opt<T> {
return { defaultValue, item }
}
Subsequently, define func()
as follows:
const func: <const T extends
Record<keyof T, {
label: string, options: readonly Opt<any>[]
}>>(
renderItems: T
) => void = (renderItems) => { }
This allows calling func()
like this:
func({
prop1: {
label: "example",
options: [
opts("example string", value => { }),
// ^?(parameter) value: string
opts(new Date(), value => { })
// ^?(parameter) value: Date
]
},
prop2: {
label: "example 2",
options: [
opts(1234, value => { }),
// ^?(parameter) value: number
]
},
});
Consequently, inference occurs precisely where needed, deducing a type T
structured as:
{
readonly prop1: {
readonly label: "example";
readonly options: readonly [Opt<string>, Opt<Date>];
};
readonly prop2: {
readonly label: "example 2";
readonly options: readonly [Opt<number>];
};
}
It presents a workaround solution, despite the manual addition of opts()
; yet ensures successful inference!
Explore the code on the TypeScript Playground