TypeScript has gained immense popularity as an extension of JavaScript. It offers powerful features such as static typing and improved tooling for better code maintenance and development experience.
One of the key benefits of using TypeScript is its ability to perform type-checking during development. This can help you catch errors early and generate more efficient JavaScript code.
TypeScript’s Check Type is an integral system within the TypeScript language that verifies the data type of a variable or object. This system offers a wide array of advanced tools to handle complex scenarios such as type guards and conditional types. By providing early error detection, improved code efficiency, and enhanced developer tooling, TypeScript’s Check Type system is instrumental in creating maintainable, large-scale JavaScript applications.
In this 12-minute read, we’ll begin by discussing basic type check functions in TypeScript, and gradually move on to more complex topics like Type Guards, runtime type checks, and advanced type validation concepts.
We’ll wrap the article up by answering some of the most frequently asked questions regarding Typescript type checks.
So, get ready to refine your TypeScript skills and build more robust, efficient applications!
What is TypeScript Type Checking?
TypeScript is a strongly typed language that is a superset of JavaScript. It offers built-in type-checking mechanisms to catch errors during web development and uses a structural type system that allows for a rich and expressive way to define and validate the shape of your objects.
This improves your code readability and understanding. More importantly, it reduces the chance of runtime errors.
Basic Type Checks in TypeScript
Let’s start by looking at some fundamental type checks in TypeScript, including operators like typeof and instanceof.
These can be used to inspect the types of various constructs such as variables, objects, classes, and arrays.
1. The ‘typeof’ Operator
The typeof operator is used to check the type of a given value. It’s particularly useful when dealing with primitive types like string, number, boolean, and symbol. But, it’ll not give much information about custom types or interfaces.
For example, consider the following:
The output:
Here are some more examples you can try in your IDE:
let myString = 'Hello, world!';
console.log(typeof myString); // Outputs: 'string'
let myNumber = 123;
console.log(typeof myNumber); // Outputs: 'number'
2. The ‘instanceof’ Operator
The instanceof operator is used to check if an object is an instance of a particular class. It returns a boolean value true if the object is of the specified object type and false otherwise.
For example:
Output:
You can practice with this example:
class MyClass {
constructor(public name: string) {}
}
let myInstance = new MyClass('Test');
console.log(myInstance instanceof MyClass); // Outputs: true
But there’s a catch with the instanceof operator — it isn’t foolproof and can sometimes return false positives with a conditional type. A safer alternative to using the instanceof operator is to create a class constant with the class name inside the class and check against that using the in operator. More on this later.
Typescript Check Type: Some Basic Examples
In the previous section, we developed an understanding of type checking in TypeScript and introduced the fundamental operators that make it possible. Building on that foundation, let’s now see how these principles apply in practice, across a range of data types in TypeScript.
1. Primitive Types
TypeScript has primitive types that include string, number, boolean, null, and undefined. Type checking in TypeScript makes sure variable types are assigned the correct data types, maintaining the consistency of your code.
Here’s what that looks like:
// Primitive Types
// Check typeof string
let myString: string = 'Hello TypeScript';
console.log(typeof myString); // Outputs: 'string'
// Check typeof number
let myNumber: number = 42;
console.log(typeof myNumber); // Outputs: 'number'
// Check typeof boolean
let myBoolean: boolean = true;
console.log(typeof myBoolean); // Outputs: 'boolean'
// Check typeof null
let myNull: null = null;
console.log(myNull === null); // Outputs: true
// Check typeof undefined
let myUndefined: undefined = undefined;
console.log(myUndefined === undefined); // Outputs: true
This will output the types of myString, myNumber, myBoolean, myNull, and myUndefined.
2. Basic Types
TypeScript doesn’t stop with just JavaScript’s primitive types. It offers more types like any, unknown, tuple, and enum. With these types, you get more control and flexibility in your code. And, you can apply the typeof operator to these.
Here are some examples:
// Basic Types
// Check typeof any
let myAny: any = 42;
myAny = 'Hello';
console.log(typeof myAny); // Outputs: 'string' after the value of myAny is changed to 'Hello'
// Check typeof unknown
let myUnknown: unknown = 42;
if (typeof myUnknown === 'number') {
myUnknown += 10;
}
console.log(myUnknown); // Outputs: 52
The above code will return two outputs: the type of myAny and the current value of myUnknown.
Check out the output of the code below:
3. Complex Types
TypeScript also offers complex types such as object, function, class, interface, and custom types. These types allow you to create more abstract and expressive code structures.
Here’s how to work with these:
// Complex Types
// Check typeof object
let myObject: { id: number; name: string } = {
id: 1,
name: 'John Doe',
};
console.log(typeof myObject); // Outputs: 'object'
// Check typeof function
let myFunction: (a: number, b: number) => number = (a, b) => {
return a + b;
};
console.log(typeof myFunction); // Outputs: 'function'
// Check typeof class
class MyClass {
constructor(public id: number, public name: string) {} // Constructor function
}
let myInstance: MyClass = new MyClass(1, 'Jane Doe');
console.log(myInstance instanceof MyClass); // Outputs: true
In the above code, the two typeof operators check for return strings with the name of the data type of the variable. The instanceof operator returns True.
Here’s the complete output:
Special Type Check Functions
Even though TypeScript doesn’t offer built-in functions specifically for type checking, it does include JavaScript’s global functions like Array.isArray() and isNaN().
Let’s take a closer look at these and some other type check functions:
1. Array.isArray()
This function is used to verify if a variable is an array. Here’s a simple example of how Array.isArray() works:
let myVar: any = [1, 2, 3];
if (Array.isArray(myVar)) {
console.log("myVar is an array");
}
In the above code, Array.isArray(myVar) returns True if myVar is an array. Here’s the output:
2. isNaN()
This function checks if a variable is NaN (Not a Number). Note that isNaN() will return true for non-numeric values, which can sometimes lead to unexpected results.
Let’s see this in action:
let myVar1: any = NaN;
if (isNaN(myVar1)) {
console.log("myVar1 is NaN");
}
let myVar2: any = "Hello";
if (isNaN(myVar2)) {
console.log("myVar2 is NaN");
}
The above code gives the following output:
3. Number.isNaN()
This is a more reliable way to check if a variable is NaN. Unlike isNaN(), Number.isNaN() will only return true for NaN.
let myVar1: number = NaN;
if (Number.isNaN(myVar1)) {
console.log("myVar1 is NaN");
}
let myVar2: any = "Hello";
if (Number.isNaN(myVar2)) {
console.log("myVar2 is NaN");
}
Let’s see the above code in action:
4. Number.isInteger()
This function is used to check if a variable is an integer.
let myVar: any = 123;
if (Number.isInteger(myVar)) {
console.log("myVar is an integer");
}
The output of the above code is shown below:
Type Guards in TypeScript
Type guards are a powerful feature of TypeScript that allow you to work with different types in a type-safe way. They are used to narrow down the type of a variable or object within a certain scope, such as within an if-statement block or a loop.
There are several ways to create type guards in TypeScript:
1. Using ‘typeof’ Operator
This is a JavaScript operator that can be used to check the type of a variable. Itworks well with primitive types like strings, numbers, and booleans. Let’s see an example:
let variable: any = "Hello";
if (typeof variable === 'string') {
// In this block, variable is treated as a string
console.log(variable.toUpperCase()); // You can access string-specific methods or properties
}
In this example, if the typeof check determines that variable is of type string, you can safely treat it as a string within the corresponding block. You can use string-specific methods or properties on variable because TypeScript recognizes that it is a string within that scope.
Here’s the output of the above code:
2. Using ‘instanceof’ Operator
This is another JavaScript operator that can be used to check if an object is an instance of a specific class. For example, you can use this to call specific methods that only exist in a specific class:
class MyClass {
name: string;
constructor(name: string) {
this.name = name;
}
}
const myInstance = new MyClass("John");
if (myInstance instanceof MyClass) {
// In this block, instance is treated as an instance of MyClass
console.log(myInstance.name); // You can access properties or methods specific to MyClass
}
In this example, the instanceof check verifies whether myInstance is an instance of the MyClass class. If the check passes, you can safely access properties or methods specific to MyClass on the instance object.
The output of the above code is shown below:
3. Using the ‘in’ Operator
The in operator in TypeScript can be used for type checking, particularly when dealing with objects and their properties. While it doesn’t directly check the data type of a variable, it checks whether a property exists in an object. This can be used to create type guards that differentiate between different types based on the existence of a property.
For instance, the following code checks whether a function startEngine() exists in the object vehicle:
let vehicle = {
brand: "Toyota",
model: "Corolla",
startEngine: function() { console.log("Engine started"); }
};
if ('startEngine' in vehicle) {
vehicle.startEngine(); // TypeScript knows 'startEngine' is a property of 'vehicle'
} else {
console.log("This vehicle can't start");
}
Here’s the output:
4. Using a User-Defined Type Guard Function with Type Predicates
You can also create a function that checks for a specific property or method on an object to determine its type. These user-defined type guards should return a boolean and use a special return type syntax that tells TypeScript that if the function returns true, then the object is of a certain type. This special return type syntax is known as a type predicate.
A type predicate is expressed as argumentName is Type. It signals to the TypeScript compiler what type a particular value is. When used in a type guard function, it allows TypeScript to narrow down the type of a variable or object within the scope where the function is used.
But it must use the is operator instead of the as operator for the check. Let’s understand this with an example:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function isFish(obj: any): obj is Fish {
return obj.swim !== undefined;
}
const myAnimal: unknown = { swim: () => console.log("Swimming") };
if (isFish(myAnimal)) {
myAnimal.swim(); // Safe to call, since we know myAnimal is of type Fish
}
In the above example, obj is Fish is the type predicate. If the isFish function returns true, TypeScript will know that the obj variable is of type Fish within the if-statement block. This is because the type predicate obj is Fish tells TypeScript that if isFish returns true, then obj is of type Fish.
Here’s the output of the above code:
Runtime Type Checks in TypeScript
For complex types, TypeScript’s type system may fall short at runtime. This is when runtime type checks become handy. You can write manual runtime checks for each custom interface and property, but this can be tedious, error-prone, and not entirely foolproof.
Thankfully, Typescript gives you access to libraries to perform more robust runtime validation. Let’s see a couple of them.
1. ‘io-ts’ Library
The io-ts library is a practical solution for creating runtime type checks. It allows you to define Codecs which describe your data structure and can be used to validate any Typescript object at runtime. The example below shows how you can use io-ts to validate an object against the MyInterface type:
import * as t from 'io-ts'
const MyInterface = t.type({
prop1: t.string,
prop2: t.number
})
const obj: unknown = { prop1: "hello", prop2: 42 }
if (MyInterface.is(obj)) {
console.log(`Prop1: ${obj.prop1}, Prop2: ${obj.prop2}`);
}
In this code, MyInterface is a runtime type t representing objects of the form { prop1: string, prop2: number }.
2. ‘runtypes’ Library
Similarly, the runtypes library offers a Runtype construct, where you define your type and use it to validate your data at runtime:
import { Record, String, Number } from 'runtypes'
const MyInterface = Record({
prop1: String,
prop2: Number
})
const obj: unknown = { prop1: "hello", prop2: 42 }
if (MyInterface.guard(obj)) {
console.log(`Prop1: ${obj.prop1}, Prop2: ${obj.prop2}`);
}
In this example, MyInterface is a runtype representing an object of the form { prop1: string, prop2: number }.
Advanced Type Validation Concepts in TypeScript
In this section, we’ll explore advanced type validation concepts in TypeScript, covering Union and Intersection Types, Discriminated Unions, and Index Signatures. These concepts will enhance your ability to create robust, error-proof code.
1. Union and Intersection Types
Union and Intersection Types allow you to combine different types to create more flexible and precise object types. Union Types are denoted with a | symbol and represent a variable that can have one of several types.
On the other hand, Intersection Types that are denoted with an & symbol combine multiple types into a single entity. Both of these are forms of compile-time type checking.
Here’s an example code showing union and intersection types:
type Shape = { name: string };
type Circle = Shape & { radius: number };
type Square = Shape & { side: number };
type Geometry = Circle | Square;
function getShapeInfo(shape: Geometry) {
console.log(`Shape: ${shape.name}`);
if ('radius' in shape) {
console.log(`Radius: ${shape.radius}`);
} else {
console.log(`Side: ${shape.side}`);
}
}
let circle: Circle = { name: 'circle', radius: 5 };
let square: Square = { name: 'square', side: 4 };
getShapeInfo(circle);
getShapeInfo(square);
In this TypeScript code, Circle and Square are intersection types, combining a base Shape type with their own properties. The function getShapeInfo uses a union type Geometry and checks whether the shape is a Circle or Square to display the appropriate details.
Here’s the output:
2. Discriminated Unions
Discriminated Unions, also known as tagged unions or algebraic data types, are a way to create a common tag between various types. This makes it easier to perform type checking and runtime checks safely.
Consider the following example:
interface Dog {
type: "dog";
bark(): void;
}
interface Cat {
type: "cat";
meow(): void;
}
type Animal = Dog | Cat; // Animal is a discriminated union
function handleAnimal(animal: Animal) {
if (animal.type === "dog") {
animal.bark(); // Safe to call since we checked the type
} else {
animal.meow(); // Also safe, the type must be "cat"
}
}
let dog: Dog = {
type: "dog",
bark: () => console.log("Woof!")
};
let cat: Cat = {
type: "cat",
meow: () => console.log("Meow!")
};
handleAnimal(dog); // Prints "Woof!"
handleAnimal(cat); // Prints "Meow!”
The above code uses discriminated unions to distinguish between Dog and Cat types. The function handleAnimal takes an Animal type (which could be either a Dog or Cat), checks its type property, and invokes the appropriate method, ensuring type safety.
Here’s the output of the code:
3. Index Signatures
Index Signatures offer a means to define the type of an object’s keys and their corresponding values. So, they provide support for declaring more flexible object types.
interface Dictionary {
[key: string]: string;
}
const myDictionary: Dictionary = {
prop1: "A",
prop2: "B",
};
// Iterate over object keys
for (const key in myDictionary) {
if (myDictionary.hasOwnProperty(key)) {
// We can safely assume myDictionary[key] is a string
console.log(myDictionary[key].toUpperCase());
}
}
The above code defines an interface Dictionary that allows any string as a key and requires string values. An instance of Dictionary named myDictionary is created with two properties.
The code then iterates over the keys of myDictionary, checks if the key truly belongs to myDictionary (not inherited), and then logs the corresponding value in uppercase.
Here’s the code in action:
Working with Validation Libraries and Custom Solutions in TypeScript
As you start writing more advanced TypeScript codes, you’ll inevitably encounter the need for robust data validation. You will have to regularly consult official documentation, learn advanced techniques through tutorials, and utilize external validation libraries for dynamic type-checking. This section provides essential guidance to enhance your proficiency and ensure you write robust, reliable TypeScript code.
1. Use Official Documentation and Tutorials
When working with TypeScript, it is essential to properly understand basic types, complex types, and type-checking. The official documentation provides comprehensive information on all fundamental concepts.
Besides the documentation, you can also find various tutorials that can help enhance your programming skills.
2. Using Validation Libraries
To validate the code in real-time, you might require more dynamic solutions for type-checking and validation. Several validation libraries are available for TypeScript, catering to the unique needs of different projects.
Some of these libraries translate TypeScript type information into runtime information, enabling you to validate data structures much more effectively.
Such libraries often deal with index signatures, filtering various objects based on user-defined properties. For instance, validating a person object might involve checking its properties such as name, age, or gender.
Final Thoughts
TypeScript’s type-checking system is an immensely powerful tool that extends beyond mere JavaScript enhancements, introducing static typing, advanced type checks, and other functions for optimized code performance.
Furthermore mastering this system enables you to write more efficient, safer code, catching errors during development and producing a superior final product.
In this 12-minute read, we delved into the basic type checks using operators like typeof and instanceof. We also discussed more complex scenarios utilizing type guards, conditional types, and even runtime checks.
You now have the necessary understanding to apply TypeScript’s robust type-checking features.
As you move forward, remember this guide is your companion in your TypeScript journey.
However, mastering TypeScript’s type checks doesn’t end here. Encourage yourself to continually practice and experiment with real-world scenarios. Utilize additional resources like official TypeScript documentation, and engage in developer community forums for a wider perspective.
And, for a deeper dive into the essentials of data literacy and how it can significantly enhance your business operations, listen to our podcast.
Frequently Asked Questions
How to Check Datatype in TypeScript?
TypeScript provides several ways to check the datatype of a variable. The typeof operator can be used to check the datatype of a variable at runtime. For example:
let value: any = "Hello, World!";
if (typeof value === "string") {
console.log("Value is a string");
}
In this example, typeof value returns the string “string“, which is then compared to the string “string” to check if value is a string.
Here is the output:
What Type is an Object in TypeScript?
In TypeScript, an object can be of any type that is defined as an interface or a type. For example:
interface Person {
name: string;
age: number;
}
let person: Person = { name: "John", age: 30 };
console.log(typeof person);
In this example, person is an object of type Person. Let’s see what output we get:
How to Compare Types in Typescript?
Comparing types in TypeScript involves checking their structure rather than their nominal type. To compare types, you can use the type guard pattern, but they may have limitations with more complex types. Union types or conditional types might be necessary for advanced use cases.
Here’s an example of using a type guard to compare types:
interface Cat {
meow: () => void;
}
interface Dog {
bark: () => void;
}
function isCat(pet: any): pet is Cat {
return (pet as Cat).meow !== undefined;
}
let pet1: Cat = { meow: () => console.log("Meow!") };
let pet2: Dog = { bark: () => console.log("Woof!") };
function makePetSound(pet: Cat | Dog) {
if (isCat(pet)) {
pet.meow(); // TypeScript knows that 'pet' is a 'Cat' here
} else {
pet.bark(); // TypeScript knows that 'pet' is a 'Dog' here
}
}
makePetSound(pet1); // Will print "Meow!"
makePetSound(pet2); // Will print "Woof!"
In the above code, we defined two objects pet1 and pet2 for Cat and Dog respectively. The makePetSound function will call the correct method based on whether the passed-in pet is a cat or a dog.
Let’s see the code in action:
What is the ‘is’ Keyword in TypeScript?
The is keyword in TypeScript is used to denote a type predicate within a function’s return type annotation. It is commonly used in custom type guards to let TypeScript know that the returned boolean value informs the type checker about the variable’s true type.