Let's consider the function f, defined as follows:
function f<T extends Fields = Fields>(props: Props<T>) {
return null;
}
In this context, T represents a generic type that extends Fields. The concept of Fields is captured by the following definition:
export type Fields = { [key: string]: unknown };
Additionally, the Props interface is outlined as:
export interface Props<T extends Fields = Fields> {
fields: Config<T>;
onSubmit?: (values: Values<T>) => void;
}
The Props interface accommodates a generic type T which extends Fields. It consists of two key properties: fields and onSubmit. The fields property conforms to the type Config<T>, while the onSubmit property encapsulates an optional function that accepts values of type Values<T> and returns void.
To provide further clarification, let's explore the definitions of Config and Values:
type BaseProps<T> = {
initialValue: T;
hidden?: boolean;
};
export interface TextInput extends BaseProps<string>, TextInputProps {
type: 'text';
}
export interface Checkbox extends BaseProps<boolean> {
type: 'checkbox';
}
type Config<T> = { [K in keyof T]: TextInput | Checkbox };
export type Values<T extends Fields> = {
[K in keyof T]: Config<T>[K]['initialValue'];
};
In essence, Config<T> generates a mapped type where each key in T corresponds to either a TextInput or Checkbox. On the other hand, Values<T> transforms each key in T into the initial value associated with the respective field in Config<T>.
Based on these definitions, function f expects props of type Props containing information about form fields and an optional submit function. The fields property leverages a mapped type (Config<T>), while the initial values are extracted using another mapped type (Values<T>).
A central question emerges regarding the accuracy of automatic type inference for Values. Currently, the type inferred for values.age is string | boolean
. This ambiguity arises from the 'or' operator within Config, allowing for either a TextInput or Checkbox.
This concern delves beyond technical considerations, touching upon design implications. Are there structural adjustments that can refine type inference precision? Or does this inherent ambiguity align with the existing design?
In essence, we aim to refine the type inference mechanism to ascertain the precise type of values.age.
f({
fields: {
name: {
type: 'text',
initialValue: 'John Doe',
},
age: {
initialValue: true,
type: 'checkbox',
},
},
onSubmit: (values) => {
console.log(values.age);
},
});
The comprehensive content is provided here for easy reference.
type BaseProps<T> = {
initialValue: T;
hidden?: boolean;
};
export interface TextInput extends BaseProps<string> {
type: 'text';
}
export interface Checkbox extends BaseProps<boolean> {
type: 'checkbox';
}
type Config<T> = { [K in keyof T]: TextInput | Checkbox };
export type Fields = { [key: string]: unknown };
export type Values<T extends Fields> = {
[K in keyof T]: Config<T>[K]['initialValue'];
};
export interface Props<T extends Fields = Fields> {
fields: Config<T>;
onSubmit?: (values: Values<T>) => void;
}
function f<T extends Fields = Fields>(props: Props<T>) {
return null;
}
f({
fields: {
name: {
type: 'text',
initialValue: 'John Doe',
},
age: {
initialValue: true,
type: 'checkbox',
},
},
onSubmit: (values) => {
console.log(values.age);
},
});
Multiple approaches were attempted to enhance type inference accuracy, but unfortunately, no definitive solution was achieved.