• Advanced TypeScript Types Cheat Sheet (with Examples) πŸ›΅
  • Ibrahima Ndaw

TypeScript is a typed language that allows you to specify variable types, function parameters, returned values, and object properties.

You can think of this article as a TypeScript advanced type cheat sheet with examples

Let’s get started!

  • Intersection Types
  • Union Types
  • Generic Types
    • Generic function
    • A generic interface
    • A multiparameter generic type
  • Utility Types
    • Partial
    • Required
    • Readonly
    • Pick
    • Omit
    • Extract
    • Exclude
    • Record
    • NonNullable
  • Mapped Types
  • Type Guards(Type Protection)
    • typeof
    • instanceof
    • in
  • Conditional Types

Intersection Types

Cross typing is a way to combine multiple types into one type. This means that you can merge A given type A with type B or more and get A single type with all the attributes.

type LeftType = {
    id: number;
    left: string;
};

type RightType = {
    id: number;
    right: string;
};

type IntersectionType = LeftType & RightType;

function showType(args: IntersectionType) {
    console.log(args);
}

showType({ id: 1.left: 'test'.right: 'test' });
// Output: {id: 1, left: "test", right: "test"}
Copy the code

As you can see, IntersectionType combines two types — LeftType and RightType, and uses ampersand to form a cross type.

Union Types

Union types allow you to assign different types to the same variable

type UnionType = string | number;

function showType(arg: UnionType) {
    console.log(arg);
}

showType('test');
// Output: test

showType(7);
// Output: 7
Copy the code

The showType function is a union type function that takes a string or a number as an argument.

Generic Types

Generic types are a way of reusing parts of a given type. It helps to capture the type T passed as a parameter.

Advantages: Create reusable functions that can support multiple types of data. This allows developers to use functions based on their own data types

Generic function

function showType<T> (args: T) {
    console.log(args);
}

showType('test');
// Output: "test"

showType(1);
// Output: 1
Copy the code

How to create a generic type: You need to use <> and pass T(name is customizable) as an argument. In the 🌰 chestnut above, we add the type variable T to showType. T helps us capture the type of the argument passed by the user (for example: number/string) and we can use this type

We call the showType function a generic function because it can be applied to multiple types

A generic interface

interface GenericType<T> {
    id: number;
    name: T;
}

function showType(args: GenericType<string>) {
    console.log(args);
}

showType({ id: 1.name: 'test' });
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<number>) {
    console.log(args);
}

showTypeTwo({ id: 1.name: 4 });
// Output: {id: 1, name: 4}
Copy the code

In the chestnut above, we declare a GenericType interface that accepts the GenericType T and constrains the type of name within the interface by type T

Note: When a generic variable constrains the entire interface, it must specify a type when implemented

So we can set name to any type of value when we use it, string or number in our example

A multiparameter generic type

interface GenericType<T, U> {
    id: T;
    name: U;
}

function showType(args: GenericType<number.string>) {
    console.log(args);
}

showType({ id: 1.name: 'test' });
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<string.string[] >) {
    console.log(args);
}

showTypeTwo({ id: '001'.name: ['This'.'is'.'a'.'Test']});// Output: {id: "001", name: Array["This", "is", "a", "Test"]}
Copy the code

A generic type can accept multiple parameters. In the above code, we pass in two parameters, T and U, and use them as types of ID,name. That is, we can now use the interface and provide different types as parameters.

Utility Types

TypeScript also provides a number of handy tools that make it easier to manipulate types. To use them, you need to pass the type to <>

Partial

  • Partial<T>

Partial allows you to make all properties of type T optional. It will add a? After each field. .

interface PartialType {
    id: number;
    firstName: string;
    lastName: string;
}

/* Equivalent to interface PartialType {id? : number firstName? : string lastName? : string } */

function showType(args: Partial<PartialType>) {
    console.log(args);
}

showType({ id: 1 });
// Output: {id: 1}

showType({ firstName: 'John'.lastName: 'Doe' });
// Output: {firstName: "John", lastName: "Doe"}
Copy the code

The above code declares a PartialType interface that is used as the type of the argument to the function showType(). To make all fields optional, we use the Partial keyword and pass the PartialType type as a parameter.

Required

  • Required<T>

Make all attributes of a type mandatory

interface RequiredType {
    id: number; firstName? :string; lastName? :string;
}

function showType(args: Required<RequiredType>) {
    console.log(args);
}

showType({ id: 1.firstName: 'John'.lastName: 'Doe' });
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 });
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName
Copy the code

In the code above, the addition of Required makes all attributes mandatory, even though we made some attributes optional before using the interface. TypeScript will report an error if you omit certain mandatory parameters.

Readonly

  • Readonly<T>

All attributes of the type are converted so that they cannot be modified

interface ReadonlyType {
    id: number;
    name: string;
}

function showType(args: Readonly<ReadonlyType>) {
    args.id = 4;
    console.log(args);
}

showType({ id: 1.name: 'Doe' });
// Error: Cannot assign to 'id' because it is a read-only property.
Copy the code

We use Readonly to make the ReadonlyType attribute unmodifiable. That is, if you try to assign a new value to one of these fields, an error will be raised.

In addition, you can use the keyword readonly in front of the specified property to make it unreassigned

interface ReadonlyType {
    readonly id: number;
    name: string;
}
Copy the code

Pick

  • Pick<T, K>

This method allows you to create a new type by selecting properties as K from an existing type T

That is, some subset of a type/interface is extracted as a new type

T represents the object K to be extracted with one constraint: it must be the combined type from all property literals of T. The new type/property must be selected from K,

From T, pick a set of properties whose keys are in the union K */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
Copy the code
interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Pick<PickType, 'firstName' | 'lastName'>) {
    console.log(args);
}

showType({ firstName: 'John'.lastName: 'Doe' });
// Output: {firstName: "John"}

showType({ id: 3 });
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'
Copy the code

Pick is a little different from the tools we discussed earlier in that it takes two arguments

  • TIs the type of element to select from
  • KIs the property to select (you can select multiple fields using union types)

Omit

  • Omit<T, K>

Omit Omit is the opposite of the Pick type. Instead of selecting elements, you remove K attributes from type T.

interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Omit<PickType, 'firstName' | 'lastName'>) {
    console.log(args);
}

showType({ id: 7 });
// Output: {id: 7}

showType({ firstName: 'John' });
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'
Copy the code

Extract

  • Extract<T, U>

Extract the types in T that can be assigned to U — take the intersection

Extract allows you to construct new types by selecting common properties from two different types. That is, extract from T all the attributes that can be assigned to U.

interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExtractType = Extract<keyof FirstType, keyof SecondType>;
// Output: "id"
Copy the code

In the above code, both the FirstType and SecondType interfaces have the ID: Number attribute. Therefore, by using Extract, a new type {id:number} is extracted.

Exclude

Exclude

— Exclude from T any type that can be assigned to U.
,>

Unlike Extract, Exclude constructs a new type by excluding common attributes that already exist in two different types. It excludes all fields from T that can be assigned to U.

interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExcludeType = Exclude<keyof FirstType, keyof SecondType>;

// Output; "firstName" | "lastName"
Copy the code

As you can see from the code above, the attributes firstName and lastName do not exist in the SecondType. By using the Extract keyword, we can obtain fields that are present in T but not in U.

Record

  • Record<K,T>

This tool helps you construct a type with a set of attributes K of a given type T. Record is handy when mapping properties of one type to properties of another type.

interface EmployeeType {
    id: number;
    fullname: string;
    role: string;
}

let employees: Record<number, EmployeeType> = {
    0: { id: 1.fullname: 'John Doe'.role: 'Designer' },
    1: { id: 2.fullname: 'Ibrahima Fall'.role: 'Developer' },
    2: { id: 3.fullname: 'Sara Duckson'.role: 'Developer'}};// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }
Copy the code

The way Record works is relatively simple. In the code, it expects a number as the type, which is why we use 0, 1, and 2 as the keys for the employees variable. If you try to use a string as an attribute, you will raise an error because the attribute is an object with the ID, fullName, and Role fields given by EmployeeType.

NonNullable

  • NonNullable<T>

— Remove null and undefined from T

type NonNullableType = string | number | null | undefined;

function showType(args: NonNullable<NonNullableType>) {
    console.log(args);
}

showType('test');
// Output: "test"

showType(1);
// Output: 1

showType(null);
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.

showType(undefined);
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.
Copy the code

We pass the type NonNullableType as an argument to NonNullable, which constructs the new type by excluding null and undefined. That is, if you pass nullable values, TypeScript raises an error.

Incidentally, TypeScript applies non-null rules if you add the –strictNullChecks flag to the tsconfig file.

Mapped Types

Mapping types allow you to generate a new type from an old type.

Note that some of the advanced types described earlier are also mapping types. Such as:

/* Readonly, Partial and Pick are homomorphic, but Record is not. Because Record does not require an input type to copy attributes, it is not a homomorphism: */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
type Partial<T> = {
    [P inkeyof T]? : T[P]; };type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

Record;
Copy the code
type StringMap<T> = {
    [P in keyof T]: string;
};

function showType(arg: StringMap<{ id: number; name: string} >) {
    console.log(arg);
}

showType({ id: 1.name: 'Test' });
// Error: Type 'number' is not assignable to type 'string'.

showType({ id: 'testId'.name: 'This is a Test' });
// Output: {id: "testId", name: "This is a Test"}
Copy the code

StringMap<> converts any type passed in to a string. That is, if we use it in the showType() function, the argument we receive must be a string – otherwise, TypeScript raises an error.

Type Guards(Type Protection)

Type protection allows you to check the type of a variable or object using operators. This is a conditional block that uses typeof, Instanceof, or IN return types.

Typescript can guarantee that variables are of certain types in certain blocks. Properties of this type can be carefully referenced in this block, or methods of this type can be called

typeof

function showType(x: number | string) {
    if (typeof x === 'number') {
        return `The result is ${x + x}`;
    }
    throw new Error(`This operation can't be done on a The ${typeof x}`);
}

showType("I'm not a number");
// Error: This operation can't be done on a string

showType(7);
// Output: The result is 14
Copy the code

Code that has an ordinary JavaScript conditional block that checks the typeof the received parameter through typeof.

instanceof

class Foo {
    bar() {
        return 'Hello World'; }}class Bar {
    baz = '123';
}

function showType(arg: Foo | Bar) {
    if (arg instanceof Foo) {
        console.log(arg.bar());
        return arg.bar();
    }

    throw new Error('The type is not supported');
}

showType(new Foo());
// Output: Hello World

showType(new Bar());
// Error: The type is not supported
Copy the code

Like the previous example, this is a type protection that checks if the received argument is part of the Foo class and processes it.

in

interface FirstType {
    x: number;
}
interface SecondType {
    y: string;
}

function showType(arg: FirstType | SecondType) {
    if ('x' in arg) {
        console.log(`The property ${arg.x} exists`);
        return `The property ${arg.x} exists`;
    }
    throw new Error('This type is not expected');
}

showType({ x: 7 });
// Output: The property 7 exists

showType({ y: 'ccc' });
// Error: This type is not expected
Copy the code

Use in to check for the presence of attribute X on the parameter object.

Conditional Types

The condition type tests both types, and then selects one based on the results of that test.

A type determined by a conditional expression of the form T extends U? X: Y, that is, if type T can be assigned to type U, the resulting type is type X, otherwise it is type Y.

Conditional types make types non-unique and add flexibility to the language,

// Source code implementation
type NonNullable<T> = T extends null | undefined ? never : T;

// NotNull
      
        equivalent to NoneNullable
       ,u>
      

// Example usage
type resType = NonNullable<string | number | null | undefined>; // string|number
Copy the code

In the above code, NonNullable checks if the type is NULL and processes based on that type. As you can see, it uses JavaScript ternary operators.

Thanks for reading.