I have a function that checks to see if a generic type’s value exists in an array:
export function isValueInArray<T extends string>(
values: T[],
value: T | undefined
) {
return value ? values.includes(value) : false;
}
It would then be used like this:
export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';
// typeof buyNowStatus === Status
const isValid = isValueInArray(['OPEN'], buyNowStatus)
In this case it works in terms of auto-completing the first array argument. The issue is that we can pass in any string:
// no typescript error is thrown here
const shouldBeInvalid = isValueInArray(['OPEN', 'ANY'], buyNowStatus)
One way to fix this would be to pass the Status in as a generic:
// ts errors out properly (but we must pass in a generic)
const isInvalid = isValueInArray<Status>(['OPEN', 'ANY'], buyNowStatus)
Is there a way to achieve this without needing to pass Status
in as a generic? To somehow infer the Status
type from buyNowStatus
and for that to override the default T extends string
behaviour in isValueInArray
?
function checkStatus(status: Status) {
// no typescript error thrown here either
return isValueInArray(['ANY'], status);
}
CodePudding user response:
You need to be generic over the whole array, and then value
is typed as a derivation of that array.
You also need to make sure the array is the right type. A literal ['a', 'b']
will be inferred as string[]
. BUt you want something like Status[]
.
For example:
export function isValueInArray<T extends string[]>(
values: T,
value: T[number] | undefined
) {
return value ? values.includes(value) : false;
}
export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';
const statuses: Status[] = ['OPEN', 'ACCEPTED']
isValueInArray(statuses, 'OPEN') // fine
isValueInArray(statuses, 'foo') // error
Now I assumed that you wanted to ensure value
is a valid member type of values
. But it sounds like you want to ensure that the array values
only has strings that match the type of value
.
You can do that, too:
export function isValueInArray<
Value extends string,
Arr extends Value[]
>(
values: Arr,
value: Value | undefined
) {
return value ? values.includes(value) : false;
}
export type Status = 'OPEN' | 'CANCELED' | 'ACCEPTED' | 'INVALIDATED';
declare const status: Status | undefined
isValueInArray(['CANCELED'], status) // fine
isValueInArray(['ANY'], status) // error
Now there are two generics. Value
is the type of the second argument. And then Arr
is the first argument that must be an array of the Value
type.