TypeScript is a powerful language that adds static types to JavaScript, enabling developers to write safer and more maintainable code. One of the key features that enhance TypeScript’s robustness is type guards. Type guards allow us to narrow down the type of a variable within a conditional block, providing better type safety and reducing runtime errors. In this article, we will explore various custom type guards and demonstrate how they can be used to ensure safe type checks in TypeScript.
Understanding Type Guards
Type guards are functions or expressions that perform runtime checks to determine if a variable is of a specific type. When a type guard function returns true
, TypeScript understands that the variable has the specific type within that scope.
Creating Custom Type Guards
Let's create custom type guards for common types and scenarios, demonstrating their usage with practical examples.
Checking for undefined
The isUndefined
type guard checks if a value is undefined
.
// isUndefined.ts
export function isUndefined(value: unknown): value is undefined {
return value === undefined;
}
var x: null | undefined;
if (isUndefined(x)) {
const y = x; // y: undefined
} else {
const z = x; // z: null
}
In this example, isUndefined
helps us narrow down the type of x
to undefined
or null
.
Checking for null
The isNull
type guard checks if a value is null
.
// isNull.ts
export function isNull(value: unknown): value is null {
return value === null;
}
var x: null | undefined;
if (isNull(x)) {
const y = x; // y: null
} else {
const z = x; // z: undefined
}
Here, isNull
helps us determine whether x
is null
or undefined
.
Checking for number
The isNumber
type guard checks if a value is a number
.
// isNumber.ts
import { getStringOrNumberRandomly } from "./helpers";
export function isNumber(value: unknown): value is number {
return typeof value === "number";
}
var data: string | number = getStringOrNumberRandomly();
if (isNumber(data)) {
const y = data; // y: number
} else {
const z = data; // z: string
}
In this case, isNumber
ensures that data
is either a number
or a string
.
Checking for string
The isString
type guard checks if a value is a string
.
// isString.ts
import { getStringOrNumberRandomly } from "./helpers";
export function isString(value: unknown): value is string {
return typeof value === "string";
}
var data: string | number = getStringOrNumberRandomly();
if (isString(data)) {
const y = data; // y: string
} else {
const z = data; // z: number
}
Here, isString
confirms that data
is either a string
or a number
.
Checking for a Complex Type
The isPerson
type guard checks if an object conforms to the Person
interface. This type guard leverages the previously created type guards (isString
, isNumber
, isNull
) to validate the structure of a more complex object.
// isPerson.ts
import { isNull } from "./isNull";
import { isNumber } from "./isNumber";
import { isString } from "./isString";
type Person = {
name: string,
age: number,
email: string | null
}
function isPerson(rawValue: unknown): rawValue is Person {
const value = rawValue as Person;
return (
isString(value.name) &&
isNumber(value.age) &&
(isString(value.email) || isNull(value.email))
);
}
const person: any = {
name: 'Harry',
age: 17,
email: null
}
if (isPerson(person)) {
const y = person; // y: Person
} else {
const z = person; // z: any
}
The isPerson
type guard validates whether an object meets the structure of the Person
type. By using the isString
, isNumber
, and isNull
type guards, isPerson
checks if name
is a string, age
is a number, and email
is either a string or null. This composite type guard ensures that all properties of the Person
object are correctly typed.
In this example, isPerson
helps us confirm that an object adheres to the expected Person
interface, allowing TypeScript to understand the exact structure and types of the object's properties within the conditional block.
Conclusion
Type guards in TypeScript provide a powerful way to ensure type safety and improve code reliability. By creating custom type guards, you can handle complex type checks efficiently and avoid common pitfalls associated with dynamic types. Leveraging these techniques will help you write cleaner, more maintainable code, ultimately leading to more robust applications.
Top comments (0)