I have a custom type, let's say
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!
In Typescript, we have three ways to work with it using: typeof: the keyword helps to check values types, like boolean, string, number, etc. instanceof: the keyword to compare the object instance with a class constructor. type guards: The powerful way to check types using typescript feature language.
Use the typeof operator to check the type of a variable in TypeScript, e.g. if (typeof myVar === 'string') {} . The typeof operator returns a string that indicates the type of the value and can be used as a type guard in TypeScript.
typeof is used to differentiate between the different types in TypeScript. By the use of typeof we can differentiate between number, string, symbol, Boolean, etc. typeof can be used with any type in TypeScript, by the use of it we can re-use the code by passing any parameter type.
Code written in TypeScript is checked for errors before it is executed, during compile time.
Short answer:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With