patterntypescriptCriticalCanonical
Typescript: Check "typeof" against custom type
Viewed 0 times
typescripttypeofcheckagainsttypecustom
Problem
I have a custom type, let's say
I would like to determine if a string is part of the Fruit type. How can I accomplish this?
The following doesn't work.
Any thoughts appreciated!
export type Fruit = "apple" | "banana" | "grape";I would like to determine if a string is part of the Fruit type. How can I accomplish this?
The following doesn't work.
let myfruit = "pear";
if (typeof myfruit === "Fruit") {
console.log("My fruit is of type 'Fruit'");
}Any thoughts appreciated!
Solution
Short answer:
You can't use
Long answer follows:
You might be confused about the difference between values and types in TypeScript, especially as it relates to the
The
The line
In your code,
What you can do is check the value of
Perfect, right? Okay, maybe that seems like a lot of redundant code. Here's a less redundant way to do it. First of all, define your
You can verify that
That type guard also lets the compiler know that inside the "then" clause of the
You can't call the function directly:
But you can call it inside the "then" clause after checking it:
Which is presumably why you want to check against your custom type in the first place. So that lets you do it.
To recap: you can't use
Playground link to code
You can't use
typeof at runtime to check for interface types, which only exist at compile time. Instead you can write a user-defined type guard function to check for such types:const fruit = ["apple", "banana", "grape"] as const;
type Fruit = (typeof fruit)[number];
const isFruit = (x: any): x is Fruit => fruit.includes(x);
let myfruit = "pear";
if (isFruit(myfruit)) {
console.log("My fruit is of type 'Fruit'");
}Long answer follows:
You might be confused about the difference between values and types in TypeScript, especially as it relates to the
typeof operator. As you may be aware, TypeScript adds a static type system to JavaScript, and that type system gets erased when the code is transpiled. The syntax of TypeScript is such that some expressions and statements refer to values that exist at runtime, while other expressions and statements refer to types that exist only at design/compile time. Values have types, but they are not types themselves. Importantly, there are some places in the code where the compiler will expect a value and interpret the expression it finds as a value if possible, and other places where the compiler will expect a type and interpret the expression it finds as a type if possible.The
typeof operator leads a double life. The expression typeof x always expects x to be a value, but typeof x itself could be a value or type depending on the context:let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}The line
let TypeofBar = typeof bar; will make it through to the JavaScript, and it will use the JavaScript typeof operator at runtime and produce a string. But type TypeofBar = typeof bar; is erased, and it is using the TypeScript type query operator to examine the static type that TypeScript has assigned to the value named bar.In your code,
let myfruit = "pear";
if (typeof myfruit === "Fruit") { // "string" === "Fruit" ?!
console.log("My fruit is of type 'Fruit'");
}typeof myfruit is a value, not a type. So it's the JavaScript typeof operator, not the TypeScript type query operator. It will always return the value "string"; it will never be Fruit or "Fruit". You can't get the results of the TypeScript type query operator at runtime, because the type system is erased at runtime. You need to give up on the typeof operator.What you can do is check the value of
myfruit against the three known Fruit string literals... like, for example, this:let myfruit = "pear";
if (myfruit === "apple" || myfruit === "banana" || myfruit === "grape") {
console.log("My fruit is of type 'Fruit'");
}Perfect, right? Okay, maybe that seems like a lot of redundant code. Here's a less redundant way to do it. First of all, define your
Fruit type in terms of an existing array of literal values... TypeScript can infer types from values, but you can't generate values from types.const fruit = ["apple", "banana", "grape"] as const;
export type Fruit = (typeof fruit)[number];You can verify that
Fruit is the same type as you defined yourself manually. Then, for the type test, you can use a user-defined type guard like this:const isFruit = (x: any): x is Fruit => fruit.includes(x);isFruit() is a function which checks if its argument is found in the fruit array, and if so, narrows the type of its argument to Fruit. Let's see it work:let myfruit = "pear";
if (isFruit(myfruit)) {
console.log("My fruit is of type 'Fruit'");
}That type guard also lets the compiler know that inside the "then" clause of the
if statement, that myfruit is a Fruit. Imagine if you had a function that only accepts Fruit, and a value that may or may not be a Fruit:declare function acceptFruit(f: Fruit): void;
const myfruit = Math.random() < 0.5 ? "pear" : "banana";You can't call the function directly:
acceptFruit(myfruit); // error, myfruit might be "pear"But you can call it inside the "then" clause after checking it:
if (isFruit(myfruit)) {
acceptFruit(myfruit); // okay, myfruit is known to be "banana"
}Which is presumably why you want to check against your custom type in the first place. So that lets you do it.
To recap: you can't use
typeof. You can compare against strings. You can do some type inference and a type guard to eliminate duplicated code and get control flow type analysis from the compiler.Playground link to code
Code Snippets
const fruit = ["apple", "banana", "grape"] as const;
type Fruit = (typeof fruit)[number];
const isFruit = (x: any): x is Fruit => fruit.includes(x);
let myfruit = "pear";
if (isFruit(myfruit)) {
console.log("My fruit is of type 'Fruit'");
}let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}let myfruit = "pear";
if (typeof myfruit === "Fruit") { // "string" === "Fruit" ?!
console.log("My fruit is of type 'Fruit'");
}let myfruit = "pear";
if (myfruit === "apple" || myfruit === "banana" || myfruit === "grape") {
console.log("My fruit is of type 'Fruit'");
}const fruit = ["apple", "banana", "grape"] as const;
export type Fruit = (typeof fruit)[number];Context
Stack Overflow Q#51528780, score: 423
Revisions (0)
No revisions yet.