Typescript is a powerful tool that enhances Javascript with static type checking along with loads of other features. While Typescript's basic type system has a lot of value, it offers a lot more features to help you write better code. This article will touch on a few of those, generics, unions and intersection types, and type guards.
Generics
One of the reasons Typescript is so popular its templates are a lot like statically typed languages such as Java or C++. For instance, generics offer a way to define a function, class, or interface that will work with different types making them useful for creating more flexible reusable code. Below is an example of a generic function that allows for any type of input:
const printValue = <T>(value: T): void => {
console.log(value);
}
printValue("message"); // logs "message"
printValue(99); // logs 99
printValue(false); // logs false
In the example, the '' syntax defines the type of variable that can be passed into the function. In this case, it would accept any type of variable. After the parenthesis, the type of return is declared, in this case, 'void' indicates that the function doesn't return anything. In the second example below it takes in an array with any type of elements:
const returnFirst = <T>(arr: T[]): T | undefined => {
return arr[0];
}
const numbers = [1, 2, 3];
const strings = ['a', 'b', 'c'];
returnFirst(numbers);// returns 1
returnFirst(strings);// returns 'a'
Union and Intersection Types
Union and intersection types are another way that Typescript allows flexibility. Union types allow for one of several alternative types, while intersection types allow for a combination of types. Below is an example of a union type of function:
const printValue = (value: number | string) => {
console.log(value);
}
printValue("message"); // logs "message"
printValue(99); // logs 99
printValue(false); // throws error
Below, two interfaces with different properties are defined and an intersection function takes in a parameter of 'Person & User' which combines the properties of both interfaces:
interface Person {
name: string;
age: number;
}
interface User {
username: string;
id: number;
}
const printInfo = (person: Person & User) => {
console.log(`Name: ${person.name}`);
console.log(`Age: ${person.age}`);
console.log(`Username: ${person.username}`);
console.log(`Id: ${person.id}`);
};
const bob: Person & User = {
name: 'bob',
age: 25,
username: 'bob999',
id: 12345
};
printInfo(bob);//prints four key/value pairs
Type Guards
Type guards are a useful feature of Typescript that allow you to use conditional logic when using union types. When you are using type guards, the type of a variable is determined when a function is invoked and its properties are accessible.
type User = {
username: string;
isVerified: boolean;
};
function isVerified(user: User): user is User & {isVerified: true} {
return user.isVerified === true;
}
const printUserDetails = (user: User) => {
if (isVerified(user)) {
console.log(`${user.username} is verified`);
} else {
console.log(`${user.username} is not verified`);
}
};
const user1: User = { username: "jane", isVerified: true };
const user2: User = { username: "john", isVerified: false };
printUserDetails(user1);//prints: 'jane is verified'
printUserDetails(user2);//prints: 'john is not verified'
Above the return type of 'isVerified' is a type predicate, it narrows the type of argument to a more specific type where the boolean value is set to 'true'.
Typescript can be a bit difficult at first and it can sometimes feel too opinionated. But these are a few of the features that give it flexibility so that help you write robust and re-useable code and spend less time looking for bugs.
Top comments (0)