Mastering Type Guards in TypeScript: Ensuring Safe Type Checks
Tags: typescript, webdev, javascript, programming
Published: 2024-06-14 15:55:03

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`.

```typescript
// 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`.

```typescript
// 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`.

```typescript
// 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`.

```typescript
// 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.

```typescript
// 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.