I am trying to create a generic option type/interface which will be used to create specific option types. This generic type/interface takes a set of options as input. The parameter value
contains the current selection, and options
contains all the options. The parameter value
should only be allowed to hold strings contained in options
and options
should be defined from the list of options defined externally.
This is my current progress:
export type GenericListOfOptions<list> = {
type: 'list',
name: string,
value: list,
options: Array<list>, // I am not sure if this is right...
}
// Here i define my options:
const these_options = ['yes', 'no', 'maybe'] as const;
// Here i define a specific option type from the
// generic option type. This specific option type is
// defined from the 'these_options' constant.
type MySpecificOptions = GenericListOfOptions<typeof these_options[number]>
// Here i use the type to create an option object.
let my_options: MySpecificOptions = {
type: 'list',
name: 'am i going to the gym?',
value: 'no',
options: these_options, // Here is an error relating readonly properties...
// 'options' should be equal to ['yes', 'no', 'maybe'].
// I preferrably want to reuse the 'these_options' array to define the 'options' parameter.
}
The error given is:
The type 'readonly ["yes", "no", "maybe"]' is 'readonly' and cannot be assigned to the mutable type '("yes" | "no" | "maybe")[]'.ts(4104)
interfaces.ts(137, 5): The expected type comes from property 'options' which is declared here on type 'MySpecificOptions '
But maybe there are other more general errors of this idea, so any help would be greatly appreciated!
CodePudding user response:
When you initialized these_options
as
const these_options = ['yes', 'no', 'maybe'] as const;
// const these_options: readonly ["yes", "no", "maybe"]
you used a const
assertion, presumably so the compiler would remember that the elements consist of the string literal types "yes"
, "no"
, and "maybe"
. But it also has the effect of making these_options
have a readonly
tuple type, which is a subtype of the ReadonlyArray
type. It is not known to have the mutating methods of the Array
type, like the push()
method or the splice()
method, etc. ReadonlyArray<T>
is therefore wider than Array<T>
. That means Array<T>
is assignable to ReadonlyArray<T>
but not vice versa.
So given the definition of GenericListOfOptions<T>
as
type GenericListOfOptions<T> = {
type: 'list',
name: string,
value: T,
options: Array<T>,
}
then of course this will be a problem:
let my_options: MySpecificOptions = {
type: 'list',
name: 'am i going to the gym?',
value: 'no',
options: these_options // error
}
because the options
property must be an Array<"yes" | "no" | "maybe">
, but these_options
is only known to be a ReadonlyArray<"yes" | "no" | "maybe">
.
Presumably you don't actually intend to mutate the contents of the options
property of GenericListOfOptions<T>
. If so, then the simplest solution is to widen the options
property type to ReadonlyArray<T>
:
type GenericListOfOptions<T> = {
type: 'list',
name: string,
value: T,
options: ReadonlyArray<T>, // change
}
let my_options: MySpecificOptions = {
type: 'list',
name: 'am i going to the gym?',
value: 'no',
options: these_options // okay
}
On the other hand, if you do want to mutate these_options
, and you do need Array<T>
instead of Readonly<T>
, then you could do an array spread to copy the const
-asserted tuple into a mutable array:
const these_options = [...['yes', 'no', 'maybe'] as const];
// const these_options: ("yes" | "no" | "maybe")[]
And then you could leave GenericListOfOptions<T>
alone.