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
Answer from jcalz on Stack OverflowShort 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
typeof in TS:
The typeof operator in TS can be used in 2 different contexts:
- In an expression/value context to return a string of its type. This is just the JavaScript
typeofoperator and will remain after a compile. - In a type context to make the type similar to an existing expression/value. This is a TS construct to help us express ourselves more easily with certain types. This will be compiled away and not be present in the compiled JavaScript
Examples:
Expression/value context
const hi = 'hi';
const one = 1;
const obj = {};
console.log(typeof hi, typeof 1, typeof obj);
// [LOG]: "string", "number", "object"
Type context:
const obj1 = {foo: 1, bar: true};
const obj2 = {foo: 1, bar: ''};
// test has the type according to the structure of obj1
const test: typeof obj1 = {foo: 1, bar: true};
// typeof obj1 is the same as:
type sameAsTypeofObj1 = {foo: number, bar: string}
// test2 has the type according to the structure of obj1
const test2: typeof obj2 = {foo: 1, bar: true};
// In test2 we get a compile error since bar is not correct
// Since the type of obj2 is {foo: number, bar: string} we get the error:
// Type 'boolean' is not assignable to type 'string'
For your specific problem I think you should use user defined type guards. Here is an example:
type Fruit = "apple" | "banana" | "grape";
let myfruit = "apple";
// user defined type guard
function isFruit(fruit: string): fruit is Fruit {
return ["apple", "banana", "grape"].indexOf(fruit) !== -1;
}
if (isFruit(myfruit)) {
// if this condition passes
// then TS compiler knows that myfruit is of the Fruit type
console.log(`${myfruit} is a Fruit.`}
}
Custom types
How do you check if some type matches your custom type?
When should and shouldn't you create custom types? When is it overkill?
How to create Type for massive nested object that change structure constantly?
Hey!
If I have a custom type, like:
export type CustomType = {a?: string;b?: number;c?: boolean;
}
Is there anyway to check if a string variable is in the type?
Something like a function belongsToCustomType("b"). And if so can i get the type of a (in this case number).
Sorry if it sounds confusing, i'm still new to typescript and don't know the terms very well.
Thank you!
So, I have basically ran into this issue a lot where I have to set the value of a string but I want to allow only a few specific kind of strings and not allow others for which I use something like this
type SupportedPlatformsType = "darwin" | "win32" | "linux";
Now the issue I run into is that how to check if a string matches this type without having to type basically every possible value that it can have
Using an array does seem like something I can do to avoid this issue but it just seems wrong to have to create an array every time I want a union Other methods I have tried also feel wrong in some way or the other Tell me what do you do?
I'm really digging into typescript using it on a medium-sized Next js project with a ton of Node backend code. I want to do things the "right way" within type script but I often ask myself if I should be creating a custom type or not. For example, I am working with spreadsheet data from the Node xlsx library which is just of type Record<string, any> but I would like to keep track of what is specifically spreadsheet data and not some other Record<string, any> data so I made these types:
type FormattedRow = Record<string, any>; type FormattedData = FormattedRow[]
Is this unnecessary, should I just use Record<string, any>? BTW I do have JSDoc strings, I just didn't paste them here
Also, when I make a type that is essentially just an array of another type like FormattedData above, when I hover over it, I don't get any useful information, just "FormattedRow[]" so then someone would have to open the custom types file to refer to it to see what FormattedRow is. Is that bad practice? I can't find any general guides online about when its a good idea to make a custom type and when it's not necessary.
Thanks!