Issue
Having something like this
type Size = 'big' | 'small';
How can I test that 'test' if a value is Size
const test = 'equal';
if(test in Size){
//no luck, 'Size' only refers to a type, but is being used as a value here
}
Can I get the type Size transformed to array keys or an enum somehow?
Is my only option to refactor the type to an enum?
Thank you
Solution
In TypeScript, all types are erased. They are not available at runtime.
However, since the type in question, 'big' | 'small'
, is based on values, the way to go here is to start from values and use them to generate type information as needed.
First we create an array of valid size strings
// inferred type: readonly ['big', 'small']
const sizes = ['big', 'small'] as const;
The as const
type assertion above causes the precise type of each array element to be inferred thus enabling us to write
// big' | 'small'
export type Size = typeof sizes[number];
Which computes the union type of all possible array elements, in other words, Size
is the type 'big' | 'small'
.
We will use Array.prototype.includes to perform the runtime check.
Full example:
export const sizes = ['big', 'small'] as const;
export type Size = typeof sizes[number];
export function isSize(value: string): value is Size {
return sizes.includes(value as any); // unfortunate
}
const test = 'equal';
if (isSize(test)) {
console.log(true);
}
An alternative solution, which obviates not just the need for the as any
type assertion in the guard but also the guard itself, is to augment the ReadonlyArray<T>
interface provided by TypeScript by add an additional overload signature for includes
.
export const sizes: readonly ['big', 'small'] = ['big', 'small'] as const;
export type Size = typeof sizes[number];
declare const test: string;
if (sizes.includes(test)) {
console.log(test);
}
declare global {
interface ReadonlyArray<T> {
includes<T extends U, U>(
this: readonly T[],
searchElement: U,
fromIndex?: number
): searchElement is T;
}
}
If you find both of these options distasteful, you can generalize isSize
to an isOneOf
(name it as you like) and delegate to it, which obviates the type assertion and the type augmentation by passing an additional parameter.
export const sizes = ['big', 'small'] as const;
export type Size = typeof sizes[number];
export function isOneOf<T extends readonly U[], U>(
values: T,
value: U
): value is T[number] {
return values.includes(value);
}
export function isSize(value: string): value is Size {
return isOneOf(sizes, value);
}
let test = 'equal';
if (isSize(test)) {
var x = test;
console.log(true);
}
Answered By - Aluan Haddad
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.