One place for hosting & domains

      TypeScript

      How To Use Functions in TypeScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Creating and using functions is a fundamental aspect of any programming language, and TypeScript is no different. TypeScript fully supports the existing JavaScript syntax for functions, while also adding type information and function overloading as new features. Besides providing extra documentation to the function, type information also reduces the chances of bugs in the code because there’s a lower risk of passing invalid data types to a type-safe function.

      In this tutorial, you will start by creating the most basic functions with type information, then move on to more complex scenarios, like using rest parameters and function overloading. You will try out different code samples, which you can follow in your own TypeScript environment or the TypeScript Playground, an online environment that allows you to write TypeScript directly in the browser.

      Prerequisites

      To follow this tutorial, you will need:

      • An environment in which you can execute TypeScript programs to follow along with the examples. To set this up on your local machine, you will need the following:
      • If you do not wish to create a TypeScript environment on your local machine, you can use the official TypeScript Playground to follow along.
      • You will need sufficient knowledge of JavaScript, especially ES6+ syntax, such as destructuring, rest operators, and imports/exports. If you need more information on these topics, reading our How To Code in JavaScript series is recommended.
      • This tutorial will reference aspects of text editors that support TypeScript and show in-line errors. This is not necessary to use TypeScript, but does take more advantage of TypeScript features. To gain the benefit of these, you can use a text editor like Visual Studio Code, which has full support for TypeScript out of the box. You can also try out these benefits in the TypeScript Playground.

      All examples shown in this tutorial were created using TypeScript version 4.2.2.

      Creating Typed Functions

      In this section, you will create functions in TypeScript, and then add type information to them.

      In JavaScript, functions can be declared in a number of ways. One of the most popular is to use the function keyword, as is shown in the following:

      function sum(a, b) {
        return a + b;
      }
      

      In this example, sum is the name of the function, (a, b) are the arguments, and {return a + b;} is the function body.

      The syntax for creating functions in TypeScript is the same, except for one major addition: You can let the compiler know what types each argument or parameter should have. The following code block shows the general syntax for this, with the type declarations highlighted:

      function functionName(param1: Param1Type, param2: Param2Type): ReturnType {
        // ... body of the function
      }
      

      Using this syntax, you can then add types to the parameters of the sum function shown earlier:

      function sum(a: number, b: number) {
        return a + b;
      }
      

      This ensures that a and b are number values.

      You can also add the type of the returned value:

      function sum(a: number, b: number): number {
        return a + b;
      }
      

      Now TypeScript will expect the sum function to return a number value. If you call your function with some parameters and store the result value in a variable called result:

      const result = sum(1, 2);
      

      The result variable is going to have the type number. If you are using the TypeScript playground or are using a text editor that fully supports TypeScript, hovering over result with your cursor will show const result: number, showing that TypeScript has implied its type from the function declaration.

      If you called your function with a value that has a type other than the one expected by your function, the TypeScript Compiler (tsc) would give you the error 2345. Take the following call to the sum function:

      sum('shark', 'whale');
      

      This would give the following:

      Output

      Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

      You can use any type in your functions, not just basic types. For example, imagine you have a User type that looks like this:

      type User = {
        firstName: string;
        lastName: string;
      };
      

      You could create a function that returns the full name of the user like the following:

      function getUserFullName(user: User): string {
        return `${user.firstName} ${user.lastName}`;
      }
      

      Most of the times TypeScript is smart enough to infer the return type of functions, so you can drop the return type from the function declaration in this case:

      function getUserFullName(user: User) {
        return `${user.firstName} ${user.lastName}`;
      }
      

      Notice you removed the : string part, which was the return type of your function. As you are returning a string in the body of your function, TypeScript correctly assumes your function has a string return type.

      To call your function now, you must pass an object that has the same shape as the User type:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      function getUserFullName(user: User) {
        return `${user.firstName} ${user.lastName}`;
      }
      
      const user: User = {
        firstName: "Jon",
        lastName: "Doe"
      };
      
      const userFullName = getUserFullName(user);
      

      This code will successfully pass the TypeScript type-checker. If you hover over the userFullName constant in your editor, the editor will identify its type as string.

      Optional Function Parameters in TypeScript

      Having all parameters is not always required when creating functions. In this section, you will learn how to mark function parameters as optional in TypeScript.

      To turn a function parameter into an optional one, add the ? modifier right after the parameter name. Given a function parameter param1 with type T, you could make param1 an optional parameter by adding ?, as highlighted in the following:

      param1?: T
      

      For example, add an optional prefix parameter to your getUserFullName function, which is an optional string that can be added as a prefix to the user’s full name:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      function getUserFullName(user: User, prefix?: string) {
        return `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      }
      

      In the first highlighted part of this code block, you are adding an optional prefix parameter to your function, and in the second highlighted part you are prefixing the user’s full name with it. To do that, you are using the nullish coalescing operator ??. This way, you are only going to use the prefix value if it is defined; otherwise, the function will use an empty string.

      Now you can call your function with or without the prefix parameter, as shown in the following:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      function getUserFullName(user: User, prefix?: string) {
        return `${prefix ?? ''} ${user.firstName} ${user.lastName}`;
      }
      
      const user: User = {
        firstName: "Jon",
        lastName: "Doe"
      };
      
      const userFullName = getUserFullName(user);
      const mrUserFullName = getUserFullName(user, 'Mr. ');
      

      In this case, the value of userFullName will be Jon Doe, and the value of mrUserFullName will be Mr. Jon Doe.

      Note that you cannot add an optional parameter before a required one; it must be listed last in the series, as is done with (user: User, prefix?: string). Listing it first would make the TypeScript Compiler return the error 1016:

      Output

      A required parameter cannot follow an optional parameter. (1016)

      Typed Arrow Function Expressions

      So far, this tutorial has shown how to type normal functions in TypeScript, defined with the function keyword. But in JavaScript, you can define a function in more than one way, such as with arrow functions. In this section, you will add types to arrow functions in TypeScript.

      The syntax for adding types to arrow functions is almost the same as adding types to normal functions. To illustrate this, change your getUserFullName function into an arrow function expression:

      const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      

      If you wanted to be explicit about the return type of your function, you would add it after the (), as shown in the highlighted code in the following block:

      const getUserFullName = (user: User, prefix?: string): string => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      

      Now you can use your function exactly like before:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      
      const user: User = {
        firstName: "Jon",
        lastName: "Doe"
      };
      
      const userFullName = getUserFullName(user);
      

      This will pass the TypeScript type-checker with no error.

      Note: Remember that everything valid for functions in JavaScript is also valid for functions in TypeScript. For a refresher on these rules, check out our How To Define Functions in JavaScript tutorial.

      Function Types

      In the previous sections, you added types to the parameters and return values for functions in TypeScript. In this section, you are going to learn how to create function types, which are types that represent a specific function signature. Creating a type that matches a specific function is especially useful when passing functions to other functions, like having a parameter that is itself a function. This is a common pattern when creating functions that accept callbacks.

      The syntax for creating your function type is similar to creating an arrow function, with two differences:

      • You remove the function body.
      • You make the function declaration return the return type itself.

      Here is how you would create a type that matches the getUserFullName function you have been using:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      type PrintUserNameFunction = (user: User, prefix?: string) => string;
      

      In this example, you used the type keyword to declare a new type, then provided the type for the two parameters in the parentheses and the type for the return value after the arrow.

      For a more concrete example, imagine you are creating an event listener function called onEvent, which receives as the first parameter the event name, and as the second parameter the event callback. The event callback itself would receive as the first parameter an object with the following type:

      type EventContext = {
        value: string;
      };
      

      You can then write your onEvent function like this:

      type EventContext = {
        value: string;
      };
      
      function onEvent(eventName: string, eventCallback: (target: EventContext) => void) {
        // ... implementation
      }
      

      Notice that the type of the eventCallback parameter is a function type:

      eventCallback: (target: EventTarget) => void
      

      This means that your onEvent function expects another function to be passed in the eventCallback parameter. This function should accept a single argument of the type EventTarget. The return type of this function is ignored by your onEvent function, and so you are using void as the type.

      Using Typed Asynchronous Functions

      When working with JavaScript, it is relatively common to have asynchronous functions. TypeScript has a specific way to deal with this. In this section, you are going to create asynchronous functions in TypeScript.

      The syntax for creating asynchronous functions is the same as the one used for JavaScript, with the addition of allowing types:

      async function asyncFunction(param1: number) {
        // ... function implementation ...
      }
      

      There is one major difference between adding types to a normal function and adding types to an asynchronous function: In an asynchronous function, the return type must always be the Promise<T> generic. The Promise<T> generic represents the Promise object that is returned by an asynchronous function, where T is the type of the value the promise resolves to.

      Imagine you have a User type:

      type User = {
        id: number;
        firstName: string;
      };
      

      Imagine also that you have a few user objects in a data store. This data could be stored anywhere, like in a file, a database, or behind an API request. For simplicity, in this example you will be using an array:

      type User = {
        id: number;
        firstName: string;
      };
      
      const users: User[] = [
        { id: 1, firstName: "Jane" },
        { id: 2, firstName: "Jon" }
      ];
      

      If you wanted to create a type-safe function that retrieves a user by ID in an asynchronous way, you could do it like this:

      async function getUserById(userId: number): Promise<User | null> {
        const foundUser = users.find(user => user.id === userId);
      
        if (!foundUser) {
          return null;
        }
      
        return foundUser;
      }
      

      In this function, you are first declaring your function as asynchronous:

      async function getUserById(userId: number): Promise<User | null> {
      

      Then you are specifying that it accepts as the first parameter the user ID, which must be a number:

      async function getUserById(userId: number): Promise<User | null> {
      

      The return type of getUserById is a Promise that resolves to either User or null. You are using the union type User | null as the type parameter to the Promise generic.

      User | null is the T in Promise<T>:

      async function getUserById(userId: number): Promise<User | null> {
      

      Call your function using await and store the result in a variable called user:

      type User = {
        id: number;
        firstName: string;
      };
      
      const users: User[] = [
        { id: 1, firstName: "Jane" },
        { id: 2, firstName: "Jon" }
      ];
      
      async function getUserById(userId: number): Promise<User | null> {
        const foundUser = users.find(user => user.id === userId);
      
        if (!foundUser) {
          return null;
        }
      
        return foundUser;
      }
      
      async function runProgram() {
        const user = await getUserById(1);
      }
      

      Note: You are using a wrapper function called runProgram because you cannot use await in the top level of a file. Doing so would cause the TypeScript Compiler to emit the error 1375:

      Output

      'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. (1375)

      If you hover over user in your editor or in the TypeScript Playground, you’ll find that user has the type User | null, which is exactly the type the promise returned by your getUserById function resolves to.

      If you remove the await and just call the function directly, the Promise object is returned:

      async function runProgram() {
        const userPromise = getUserById(1);
      }
      

      If you hover over userPromise, you’ll find that it has the type Promise<User | null>.

      Most of the time, TypeScript can infer the return type of your async function, just like it does with non-async functions. You can therefore omit the return type of the getUserById function, as it is still correctly inferred to have the type Promise<User | null>:

      async function getUserById(userId: number) {
        const foundUser = users.find(user => user.id === userId);
      
        if (!foundUser) {
          return null;
        }
      
        return foundUser;
      }
      

      Adding Types to Rest Parameters

      Rest parameters are a feature in JavaScript that allows a function to receive many parameters as a single array. In this section, you will use rest parameters with TypeScript.

      Using rest parameters in a type-safe way is completely possible by using the rest parameter followed by the type of the resulting array. Take for example the following code, where you have a function called sum that accepts a variable amount of numbers and returns their total sum:

      function sum(...args: number[]) {
        return args.reduce((accumulator, currentValue) => {
          return accumulator + currentValue;
        }, 0);
      }
      

      This function uses the .reduce Array method to iterate over the array and add the elements together. Notice the rest parameter args highlighted here. The type is being set to an array of numbers: number[].

      Calling your function works normally:

      function sum(...args: number[]) {
        return args.reduce((accumulator, currentValue) => {
          return accumulator + currentValue;
        }, 0);
      }
      
      const sumResult = sum(2, 4, 6, 8);
      

      If you call your function using anything other than a number, like:

      const sumResult = sum(2, "b", 6, 8);
      

      The TypeScript Compiler will emit the error 2345:

      Output

      Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

      Using Function Overloads

      Programmers sometime need a function to accept different parameters depending on how the function is called. In JavaScript, this is normally done by having a parameter that may assume different types of values, like a string or a number. Setting multiple implementations to the same function name is called function overloading.

      With TypeScript, you can create function overloads that explicitly describe the different cases that they address, improving the developer experience by document each implementation of the overloaded function separately. This section will go through how to use function overloading in TypeScript.

      Imagine you have a User type:

      type User = {
        id: number;
        email: string;
        fullName: string;
        age: number;
      };
      

      And you want to create a function that can look up a user using any of the following information:

      • id
      • email
      • age and fullName

      You could create such a function like this:

      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      This function uses the | operator to compose a union of types for idOrEmailOrAge and for the return value.

      Next, add function overloads for each way you want your function to be used, as shown in the following highlighted code:

      type User = {
        id: number;
        email: string;
        fullName: string;
        age: number;
      };
      
      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      This function has three overloads, one for each way to retrieve a user. When creating function overloads, you add the function overloads before the function implementation itself. The function overloads do not have a body; they just have the list of parameters and the return type.

      Next, you implement the function itself, which should have a parameter list that is compatible with all function overloads. In the previous example, your first parameter can be either a number or a string, since it can be the id, the email, or the age:

      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      You therefore set the type of the idOrEmailorAge parameter in your function implementation to be number | string. This way, it is compatible with all the overloads of your getUser function.

      You are also adding an optional parameter to your function, for when the user is passing a fullName:

      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      Implementing your function could be like the following, where you are using a users array as the data store of your users:

      type User = {
        id: number;
        email: string;
        fullName: string;
        age: number;
      };
      
      const users: User[] = [
        { id: 1, email: "jane_doe@example.com", fullName: "Jane Doe" , age: 35 },
        { id: 2, email: "jon_do@example.com", fullName: "Jon Doe", age: 35 }
      ];
      
      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        if (typeof idOrEmailOrAge === "string") {
          return users.find(user => user.email === idOrEmailOrAge);
        }
      
        if (typeof fullName === "string") {
          return users.find(user => user.age === idOrEmailOrAge && user.fullName === fullName);
        } else {
          return users.find(user => user.id === idOrEmailOrAge);
        }
      }
      
      const userById = getUser(1);
      const userByEmail = getUser("jane_doe@example.com");
      const userByAgeAndFullName = getUser(35, "Jon Doe");
      

      In this code, if idOrEmailOrAge is a string, then you can search for the user with the email key. The following conditional assumes idOrEmailOrAge is a number, so it is either the id or the age, depending on if fullName is defined.

      One interesting aspect of function overloads is that in most editors, including VS Code and the TypeScript Playground, as soon as you type the function name and open the first parenthesis to call the function, a pop-up will appear with all the overloads available, as shown in the following image:

      Overloads Popup

      If you add a comment to each function overload, the comment will also be in the pop-up as a source of documentation. For example, add the following highlighted comments to the example overloads:

      ...
      /**
       * Get a user by their ID.
       */
      function getUser(id: number): User | undefined;
      /**
       * Get a user by their email.
       */
      function getUser(email: string): User | undefined;
      /**
       * Get a user by their age and full name.
       */
      function getUser(age: number, fullName: string): User | undefined;
      ...
      

      Now when you hover over these functions, the comment will show up for each overload, as shown in the following animation:

      Overloads Popup with Comments

      User-Defined Type Guards

      The last feature of functions in TypeScript that this tutorial will examine is user-defined type guards, which are special functions that allow TypeScript to better infer the type of some value. These guards enforce certain types in conditional code blocks, where the type of a value may be different depending on the situation. These are especially useful when using the Array.prototype.filter function to return a filtered array of data.

      One common task when adding values conditionally to an array is to check for some conditions and then only add the value if the condition is true. If the value is not true, the code adds a false Boolean to the array. Before using that array, you can filter it using .filter(Boolean) to make sure only truthy values are returned.

      When called with a value, the Boolean constructor returns true or false, depending on if this value is a Truthy or Falsy value.

      For example, imagine you have an array of strings, and you only want to include the string production to that array if some other flag is true:

      const isProduction = false
      
      const valuesArray = ['some-string', isProduction && 'production']
      
      function processArray(array: string[]) {
        // do something with array
      }
      
      processArray(valuesArray.filter(Boolean))
      

      While this is, at runtime, perfectly valid code, the TypeScript Compiler will give you the error 2345 during compilation:

      Output

      Argument of type '(string | boolean)[]' is not assignable to parameter of type 'string[]'. Type 'string | boolean' is not assignable to type 'string'. Type 'boolean' is not assignable to type 'string'. (2345)

      This error is saying that, at compile-time, the value passed to processArray is interpreted as an array of false | string values, which is not what the processArray expected. It expects an array of strings: string[].

      This is one case where TypeScript is not smart enough to infer that by using .filter(Boolean) you are removing all falsy values from your array. However, there is one way to give this hint to TypeScript: using user-defined type guards.

      Create a user-defined type guard function called isString:

      function isString(value: any): value is string {
        return typeof value === "string"
      }
      

      Notice the return type of the isString function. The way to create user-defined type guards is by using the following syntax as the return type of a function:

      parameterName is Type
      

      Where parameterName is the name of the parameter you are testing, and Type is the expected type the value of this parameter has if this function returns true.

      In this case, you are saying that value is a string if isString returns true. You are also setting the type of your value parameter to any, so it works with any type of value.

      Now, change your .filter call to use your new function instead of passing it the Boolean constructor:

      const isProduction = false
      
      const valuesArray = ['some-string', isProduction && 'production']
      
      function processArray(array: string[]) {
        // do something with array
      }
      
      function isString(value: any): value is string {
        return typeof value === "string"
      }
      
      processArray(valuesArray.filter(isString))
      

      Now the TypeScript compiler correctly infers that the array passed to processArray only contains strings, and your code compiles correctly.

      Conclusion

      Functions are the building block of applications in TypeScript, and in this tutorial you learned how to build type-safe functions in TypeScript and how to take advantage of function overloads to better document all variants of a single function. Having this knowledge will allow for more type-safe and easy-to-maintain functions throughout your code.

      For more tutorials on TypeScript, check out our TypeScript Topic page.



      Source link

      How To Create Custom Types in TypeScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      TypeScript is an extension of the JavaScript language that uses JavaScript’s runtime with a compile-time type checker. This combination allows developers to use the full JavaScript ecosystem and language features, while also adding optional static type-checking, enums, classes, and interfaces on top of it.

      Though the pre-made, basic types in TypeScript will cover many use cases, creating your own custom types based on these basic types will allow you to ensure the type checker validates the data structures specific to your project. This will reduce the chance of bugs in your project, while also allowing for better documentation of the data structures used throughout the code.

      This tutorial will show you how to use custom types with TypeScript, how to compose those types together with unions and intersections, and how to use utility types to add flexibility to your custom types. It will lead you through different code samples, which you can follow in your own TypeScript environment or the TypeScript Playground, an online environment that allows you to write TypeScript directly in the browser.

      Prerequisites

      To follow this tutorial, you will need:

      • An environment in which you can execute TypeScript programs to follow along with the examples. To set this up on your local machine, you will need the following:
      • If you do not wish to create a TypeScript environment on your local machine, you can use the official TypeScript Playground to follow along.
      • You will need sufficient knowledge of JavaScript, especially ES6+ syntax, such as destructuring, rest operators, and imports/exports. If you need more information on these topics, reading our How To Code in JavaScript series is recommended.
      • This tutorial will reference aspects of text editors that support TypeScript and show in-line errors. This is not necessary to use TypeScript, but does take more advantage of TypeScript features. To gain the benefit of these, you can use a text editor like Visual Studio Code, which has full support for TypeScript out of the box. You can also try out these benefits in the TypeScript Playground.

      All examples shown in this tutorial were created using TypeScript version 4.2.2.

      Creating Custom Types

      In cases where programs have complex data structures, using TypeScript’s basic types may not completely describe the data structures you are using. In these cases, declaring your own type will help you address the complexity. In this section, you are going create types that can be used to describe any object shape you need to use in your code.

      Custom Type Syntax

      In TypeScript, the syntax for creating custom types is to use the type keyword followed by the type name and then an assignment to a {} block with the type properties. Take the following:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      

      The syntax resembles an object literal, where the key is the name of the property and the value is the type this property should have. This defines a type Programmer that must be an object with the name key that holds a string value and a knownFor key that holds an array of strings.

      As shown in the earlier example, you can use ; as the separator between each property. It is also possible to use a comma, ,, or to completely omit the separator, as shown here:

      type Programmer = {
        name: string
        knownFor: string[]
      };
      

      Using your custom type is the same as using any of the basic types. Add a double colon and then add your type name:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace',
        knownFor: ['Mathematics', 'Computing', 'First Programmer']
      };
      

      The ada constant will now pass the type checker without throwing an error.

      If you write this example in any editor with full support of TypeScript, like in the TypeScript Playground, the editor will suggest the fields expected by that object and their types, as shown in the following animation:

      An animation showing suggestions to add the

      If you add comments to the fields using the TSDoc format, a popular style of TypeScript comment documentation, they are also suggested in code completion. Take the following code with explanations in comments:

      type Programmer = {
        /**
         * The full name of the Programmer
         */
        name: string;
        /**
         * This Programmer is known for what?
         */
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace',
        knownFor: ['Mathematics', 'Computing', 'First Programmer']
      };
      

      The commented descriptions will now appear with the field suggestions:

      Code completion with TSDoc comments

      When creating an object with the custom type Programmer, if you assign a value with an unexpected type to any of the properties, TypeScript will throw an error. Take the following code block, with a highlighted line that does not adhere to the type declaration:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: true,
        knownFor: ['Mathematics', 'Computing', 'First Programmer']
      };
      

      The TypeScript Compiler (tsc) will show the error 2322:

      Output

      Type 'boolean' is not assignable to type 'string'. (2322)

      If you omitted any of the properties required by your type, like in the following:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace'
      };
      

      The TypeScript Compiler will give the error 2741:

      Output

      Property 'knownFor' is missing in type '{ name: string; }' but required in type 'Programmer'. (2741)

      Adding a new property not specified in the original type will also result in an error:

      type Programmer = {
        name: string;
        knownFor: string[];
      };
      
      const ada: Programmer = {
        name: "Ada Lovelace",
        knownFor: ['Mathematics', 'Computing', 'First Programmer'],
        age: 36
      };
      

      In this case, the error shown is the 2322:

      Output

      Type '{ name: string; knownFor: string[]; age: number; }' is not assignable to type 'Programmer'. Object literal may only specify known properties, and 'age' does not exist in type 'Programmer'.(2322)

      Nested Custom Types

      You can also nest custom types together. Imagine you have a Company type that has a manager field that adheres to a Person type. You could create those types like this:

      type Person = {
        name: string;
      };
      
      type Company = {
        name: string;
        manager: Person;
      };
      

      Then you could create a value of type Company like this:

      const manager: Person = {
        name: 'John Doe',
      }
      
      const company: Company = {
        name: 'ACME',
        manager,
      }
      

      This code would pass the type checker, since the manager constant fits the type designated for the manager field. Note that this uses the object property shorthand to declare manager.

      You can omit the type in the manager constant because it has the same shape as the Person type. TypeScript is not going to raise an error when you use an object with the same shape as the one expected by the type of the manager property, even if it is not set explicitly to have the Person type

      The following will not throw an error:

      const manager = {
        name: 'John Doe'
      }
      
      const company: Company = {
        name: 'ACME',
        manager
      }
      

      You can even go one step further and set the manager directly inside this company object literal:

      const company: Company = {
        name: 'ACME',
        manager: {
          name: 'John Doe'
        }
      };
      

      All these scenarios are valid.

      If writing these examples in an editor that supports TypeScript, you will find that the editor will use the available type information to document itself. For the previous example, as soon as you open the {} object literal for manager, the editor will expect a name property of type string:

      TypeScript Code Self-Documenting

      Now that you have gone through some examples of creating your own custom type with a fixed number of properties, next you’ll try adding optional properties to your types.

      Optional Properties

      With the custom type declaration in the previous sections, you cannot omit any of the properties when creating a value with that type. There are, however, some cases that require optional properties that can pass the type checker with or without the value. In this section, you will declare these optional properties.

      To add optional properties to a type, add the ? modifier to the property. Using the Programmer type from the previous sections, turn the knownFor property into an optional property by adding the following highlighted character:

      type Programmer = {
        name: string;
        knownFor?: string[];
      };
      

      Here you are adding the ? modifier after the property name. This makes TypeScript consider this property as optional and not raise an error when you omit that property:

      type Programmer = {
        name: string;
        knownFor?: string[];
      };
      
      const ada: Programmer = {
        name: 'Ada Lovelace'
      };
      

      This will pass without an error.

      Now that you know how to add optional properties to a type, it is time to learn how to create a type that can hold an unlimited number of fields.

      Indexable Types

      The previous examples showed that you cannot add properties to a value of a given type if that type does not specify those properties when it was declared. In this section, you will create indexable types, which are types that allow for any number of fields if they follow the index signature of the type.

      Imagine you had a Data type to hold an unlimited number of properties of the any type. You could declare this type like this:

      type Data = {
        [key: string]: any;
      };
      

      Here you create a normal type with the type definition block in curly brackets ({}), and then add a special property in the format of [key: typeOfKeys]: typeOfValues, where typeOfKeys is the type the keys of that object should have, and typeOfValues is the type the values of those keys should have.

      You can then use it normally like any other type:

      type Data = {
        [key: string]: any;
      };
      
      const someData: Data = {
        someBooleanKey: true,
        someStringKey: 'text goes here'
        // ...
      }
      

      Using indexable types, you can assign an unlimited number of properties, as long as they match the index signature, which is the name used to describe the types of the keys and values of an indexable type. In this case, the keys have a string type, and the values have any type.

      It is also possible to add specific properties that are always required to your indexable type, just like you could with a normal type. In the following highlighted code, you are adding the status property to your Data type:

      type Data = {
        status: boolean;
        [key: string]: any;
      };
      
      const someData: Data = {
        status: true,
        someBooleanKey: true,
        someStringKey: 'text goes here'
        // ...
      }
      

      This would mean that a Data type object must have a status key with a boolean value to pass the type checker.

      Now that you can create an object with different numbers of elements, you can move on to learning about arrays in TypeScript, which can have a custom number of elements or more.

      Creating Arrays with Number of Elements or More

      Using both the array and tuple basic types available in TypeScript, you can create custom types for arrays that should have a minimum amount of elements. In this section, you will use the TypeScript rest operator ... to do this.

      Imagine you have a function responsible for merging multiple strings. This function is going to take a single array parameter. This array must have at least two elements, each of which should be strings. You can create a type like this with the folowing:

      type MergeStringsArray = [string, string, ...string[]];
      

      The MergeStringsArray type is taking advantage of the fact that you can use the rest operator with an array type and uses the result of that as the third element of a tuple. This means that the first two strings are required, but additional string elements after that are not required.

      If an array has less than two string elements, it will be invalid, like the following:

      const invalidArray: MergeStringsArray = ['some-string']
      

      The TypeScript Compiler is going to give error 2322 when checking this array:

      Output

      Type '[string]' is not assignable to type 'MergeStringsArray'. Source has 1 element(s) but target requires 2. (2322)

      Up to this point, you have created your own custom types from a combination of basic types. In the next section, you will make a new type by composing two or more custom types together.

      Composing Types

      This section will go through two ways that you can compose types together. These will use the union operator to pass any data that adheres to one type or the other and the intersection operator to pass data that satisfies all the conditions in both types.

      Unions

      Unions are created using the | (pipe) operator, which represents a value that can have any of the types in the union. Take the following example:

      type ProductCode = number | string
      

      In this code, ProductCode can be either a string or a number. The following code will pass the type checker:

      type ProductCode = number | string;
      
      const productCodeA: ProductCode="this-works";
      
      const productCodeB: ProductCode = 1024;
      

      A union type can be created from a union of any valid TypeScript types.

      Intersections

      You can use intersection types to create a completely new type that has all the properties of all the types being intersected together.

      For example, imagine you have some common fields that always appear in the response of your API calls, then specific fields for some endpoints:

      type StatusResponse = {
        status: number;
        isValid: boolean;
      };
      
      type User = {
        name: string;
      };
      
      type GetUserResponse = {
        user: User;
      };
      

      In this case, all responses will have status and isValid properties, but only user resonses will have the additional user field. To create the resulting response of a specific API User call using an intersection type, combine both StatusResponse and GetUserResponse types:

      type ApiGetUserResponse = StatusResponse & GetUserResponse;
      

      The type ApiGetUserResponse is going to have all the properties available in StatusResponse and those available in GetUserResponse. This means that data will only pass the type checker if it satisfies all the conditions of both types. The following example will work:

      let response: ApiGetUserResponse = {
          status: 200,
          isValid: true,
          user: {
              name: 'Sammy'
          }
      }
      

      Another example would be the type of the rows returned by a database client for a query that contains joins. You would be able to use an intersection type to specify the result of such a query:

      type UserRoleRow = {
        role: string;
      }
      
      type UserRow = {
        name: string;
      };
      
      type UserWithRoleRow = UserRow & UserRoleRow;
      

      Later, if you used a fetchRowsFromDatabase() function like the following:

      const joinedRows: UserWithRoleRow = fetchRowsFromDatabase()
      

      The resulting constant joinedRows would have to have a role property and a name property that both held string values in order to pass the type checker.

      Using Template Strings Types

      Starting with TypeScript 4.1, it is possible to create types using template string types. This will allow you to create types that check specific string formats and add more customization to your TypeScript project.

      To create template string types, you use a syntax that is almost the same as what you would use when creating template string literals. But instead of values, you will use other types inside the string template.

      Imagine you wanted to create a type that passes all strings that begin with get. You would be able to do that using template string types:

      type StringThatStartsWithGet = `get${string}`;
      
      const myString: StringThatStartsWithGet="getAbc";
      

      myString will pass the type checker here because the string starts with get then is followed by an additional string.

      If you passed an invalid value to your type, like the following invalidStringValue:

      type StringThatStartsWithGet = `get${string}`;
      
      const invalidStringValue: StringThatStartsWithGet="something";
      

      The TypeScript Compiler would give you the error 2322:

      Output

      Type '"something"' is not assignable to type '`get${string}`'. (2322)

      Making types with template strings helps you to customize your type to the specific needs of your project. In the next section, you will try out type assertions, which add a type to otherwise untyped data.

      Using Type Assertions

      The any type can be used as the type of any value, which often does not provide the strong typing needed to get the full benefit out of TypeScript. But sometimes you may end up with some variables bound to any that are outside of your control. This will happen if you are using external dependencies that were not written in TypeScript or that do not have type declaration available.

      In case you want to make your code type-safe in those scenarios, you can use type assertions, which is a way to change the type of a variable to another type. Type assertions are made possible by adding as NewType after your variable. This will change the type of the variable to that specified after the as keyword.

      Take the following example:

      const valueA: any = 'something';
      
      const valueB = valueA as string;
      

      valueA has the type any, but, using the as keyword, this code coerces the valueB to have the type string.

      Note: To assert a variable of TypeA to have the type TypeB, TypeB must be a subtype of TypeA. Almost all TypeScript types, besides never, are a subtype of any, including unknown.

      Utility Types

      In the previous sections, you reviewed multiple ways to create custom types out of basic types. But sometimes you do not want to create a completely new type from scratch. There are times when it might be best to use a few properties of an existing type, or even create a new type that has the same shape as another type, but with all the properties set to be optional.

      All of this is possible using existing utility types available with TypeScript. This section will cover a few of those utility types; for a full list of all available ones, take a look at the Utility Types part of the TypeScript handbook.

      All utility types are Generic Types, which you can think of as a type that accepts other types as parameters. A Generic type can be identified by being able to pass type parameters to it using the <TypeA, TypeB, ...> syntax.

      Record<Key, Value>

      The Record utility type can be used to create an indexable type in a cleaner way than using the index signature covered previously.

      In your indexable types example, you had the following type:

      type Data = {
        [key: string]: any;
      };
      

      You can use the Record utility type instead of an indexable type like this:

      type Data = Record<string, any>;
      

      The first type parameter of the Record generic is the type of each key. In the following example, all the keys must be strings:

      type Data = Record<string, any>
      

      The second type parameter is the type of each value of those keys. The following would allow the values to be any:

      type Data = Record<string, any>
      

      Omit<Type, Fields>

      The Omit utility type is useful to create a new type based on another one, while excluding some properties you do not want in the resulting type.

      Imagine you have the following type to represent the type of a user row in a database:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      

      If in your code you are retrieving all the fields but the addressId one, you can use Omit to create a new type without that field:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithoutAddressId = Omit<UserRow, 'addressId'>;
      

      The first argument to Omit is the type that you are basing the new type on. The second is the field that you’d like to omit.

      If you hover over UserRowWithoutAddressId in your code editor, you will find that it has all the properties of the UserRow type but the ones you omitted.

      You can pass multiple fields to the second type parameter using a union of strings. Say you also wanted to omit the id field, you could do this:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithoutIds = Omit<UserRow, 'id' | 'addressId'>;
      

      Pick<Type, Fields>

      The Pick utility type is the exact opposite of the Omit type. Instead of saying the fields you want to omit, you specify the fields you want to use from another type.

      Using the same UserRow you used before:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      

      Imagine you need to select only the email key from the database row. You could create such a type using Pick like this:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithEmailOnly = Pick<UserRow, 'email'>;
      

      The first argument to Pick here specifies the type you are basing the new type on. The second is the key that you would like to include.

      This would be equivalent to the following:

      type UserRowWithEmailOnly = {
          email: string;
      }
      

      You are also able to pick multiple fields using an union of strings:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowWithEmailOnly = Pick<UserRow, 'name' | 'email'>;
      

      Partial<Type>

      Using the same UserRow example, imagine you want to create a new type that matches the object your database client can use to insert new data into your user table, but with one small detail: Your database has default values for all fields, so you are not required to pass any of them. To do this, you can use a Partial utility type to optionally include all fields of the base type.

      Your existing type, UserRow, has all the properties as required:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      

      To create a new type where all properties are optional, you can use the Partial<Type> utility type like the following:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowInsert = Partial<UserRow>;
      

      This is exactly the same as having your UserRowInsert like this:

      type UserRow = {
        id: number;
        name: string;
        email: string;
        addressId: string;
      };
      
      type UserRowInsert = {
        id?: number | undefined;
        name?: string | undefined;
        email?: string | undefined;
        addressId?: string | undefined;
      };
      

      Utility types are a great resource to have, because they provide a faster way to build up types than creating them from the basic types in TypeScript.

      Conclusion

      Creating your own custom types to represent the data structures used in your own code can provide a flexible and useful TypeScript solution for your project. In addition to increasing the type-safety of your own code as a whole, having your own business objects typed as data structures in the code will increase the overall documentation of the code-base and improve your own developer experience when working with teammates on the same code-base.

      For more tutorials on TypeScript, check out our TypeScript Topic page.



      Source link

      How To Use Basic Types in TypeScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      TypeScript is an extension of the JavaScript language that uses JavaScript’s runtime with a compile-time type checker. This combination allows developers to use the full JavaScript ecosystem and language features, while also adding optional static type-checking, enum data types, classes, and interfaces. These features provide the developer with the flexibility of JavaScript’s dynamic nature, but also allow for a more reliable codebase, where type information can be used at compile-time to detect possible issues that could cause bugs or other unexpected behavior at runtime.

      The extra type information also provides better documentation of codebases and improved IntelliSense (code completion, parameters info, and similar content assist features) in text editors. Teammates can identify exactly what types are expected for any variable or function parameter, without having to go through the implementation itself.

      This tutorial will go through type declaration and all the basic types used in TypeScript. It will lead you through examples with different code samples, which you can follow along with in your own TypeScript environment or the TypeScript Playground, an online environment that allows you to write TypeScript directly in the browser.

      Prerequisites

      To follow this tutorial, you will need:

      • An environment in which you can execute TypeScript programs to follow along with the examples. To set this up on your local machine, you will need the following.
      • If you do not wish to create a TypeScript environment on your local machine, you can use the official TypeScript Playground to follow along.
      • You will need sufficient knowledge of JavaScript, especially ES6+ syntax, such as destructuring, rest operators, and imports/exports. If you need more information on these topics, reading our How To Code in JavaScript series is recommended.
      • This tutorial will reference aspects of text editors that support TypeScript and show in-line errors. This is not necessary to use TypeScript, but does take more advantage of TypeScript features. To gain the benefit of these, you can use a text editor like Visual Studio Code, which has full support for TypeScript out of the box. You can also try out these benefits in the TypeScript Playground.

      All examples shown in this tutorial were created using TypeScript version 4.2.2.

      Declaring Variable Types in TypeScript

      When writing code in JavaScript, which is a purely dynamic language, you can’t specify the data types of variables. You create the variables and assign them a value, but do not specify a type, as shown in the following:

      const language = {
        name: "JavaScript"
      };
      

      In this code block, language is an object that holds a string value for the property name. The value type for language and its properties is not explicitly set, and this could cause confusion later if future developers do not know what kind of value language references.

      TypeScript has as a main benefit a strict type system. A statically typed language is one where the type of the variables is known at compilation time. In this section, you will try out the syntax used to specify variable types with TypeScript.

      Types are extra information that you write directly in your code. The TypeScript compiler uses this extra information to enforce the correct use of the different values depending on their type.

      Imagine working with a dynamic language, such as JavaScript, and using a string variable as if it were a number. When you do not have strict unit testing, the possible bug is only going to appear during runtime. If using the type system available with TypeScript, the compiler would not compile the code, giving an error instead, like this:

      Output

      The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. (2363)

      To declare a variable with a certain type in TypeScript, use the following syntax:

      declarationKeyword variableName: Type
      

      declarationKeyword would be something like let, var, or const. This would be followed by the variable name, a colon (:), and the type of that variable.

      Any code you write in TypeScript is, in some way, already using the type system, even if you are not specifying any types. Take this code as an example:

      let language="TypeScript";
      

      In TypeScript, this has the same meaning as the following:

      let language: string = 'TypeScript';
      

      In the first example, you did not set the type of the language variable to string, but TypeScript inferred the type because you assigned a string value when it was declared. In the second example, you are explicitly setting the type of the language variable to string.

      If you used const instead of let, it would be the following:

      const language="TypeScript";
      

      In this case, TypeScript would use the string literal TypeScript as the type of your variable, as if you typed it this way:

      const language: 'TypeScript' = 'TypeScript';
      

      TypeScript does this because, when using const, you are not going to assign a new value to the variable after the declaration, as doing this would raise an error.

      Note: If you are using an editor that supports TypeScript, hovering over the variables with your cursor will display the type information of each variable.

      If you explicitly set the type of a variable then use a different type as its value, the TypeScript Compiler (tsc) or your editor will show the error 2322. Try running the following:

      const myNumber: number="look! this is not a number :)";
      

      This will yield the following error:

      Output

      Type 'string' is not assignable to type 'number'. (2322)

      Now that you’ve tried out setting the type of a variable in TypeScript, the next section will show all the basic types supported by TypeScript.

      Basic Types Used in TypeScript

      TypeScript has multiple basic types that are used as building blocks when building more complex types. In the following sections, you are going to examine most of these types. Notice that most variables you are creating throughout this section could have their type omitted because TypeScript would be able to infer them, but you are being explicit about the types for learning purposes.

      string

      The type string is used for textual data types, like string literals or template strings.

      Try out the following code:

      const language: string = 'TypeScript';
      const message: string = `I'm programming in ${language}!`;
      

      In this code block, both language and message are assigned the string type. The template literal is still a string, even though it is determined dynamically.

      Since strings are common in JavaScript programming, this is probably one of the types you are going to use most.

      boolean

      The type boolean is used to represent true or false.

      Try out the code in the following block:

      const hasErrors: boolean = true;
      const isValid: boolean = false;
      

      Since hasErrors and isValid were declared as booleans, they can only be assigned the values true and false. Note that truthy and falsy values are not converted into their boolean equivalents and will throw an error if used with these variables.

      number

      The type number is used to represent integers and floats, as in the following:

      const pi: number = 3.14159;
      const year: number = 2021;
      

      This is another common type that is used often in JavaScript development, so this declaration will be common in TypeScript.

      bigint

      The type bigint is a type that can be used when targetting ES2020. It’s used to represent BigInt, which is a new datatype to store integers bigger than 2^53.

      Try the following code:

      const bigNumber: bigint = 9007199254740993n;
      

      Note: If this code throws an error, it is possible that TypeScript is not set up to target ES2020. This can be changed in your tsconfig.json file.

      If you are working with numbers bigger than 2^53 or with some Math libraries, bigint will be a common type declaration.

      symbol

      The symbol type is used to represent the Symbol primitive value. This will create a unique, unnamed value.

      Run the following code using the Symbol() constructor function:

      const mySymbol: symbol = Symbol('unique-symbol-value');
      

      The uniqueness of these values can be used to avoid reference collisions. For more on symbols in JavaScript, read the symbol article on Mozilla Developer Network (MDN).

      Arrays

      In TypeScript, arrays are typed based on the elements they are expected to have. There are two ways to type an array:

      • Appending [] to the expected type of the array elements. For example, if you want to type an array that holds multiple number values, you could do it like this:
      const primeNumbers: number[] = [2, 3, 5, 7, 11];
      

      If you assigned a string value to this array, TypeScript would give you an error.

      • Using the Array<T> Generic, where T is the expected type of the elements in that array. Using the previous example, it would become this:
      const primeNumbers: Array<number> = [2, 3, 5, 7, 11];
      

      Both ways are identical, so pick one and try using only that format to represent arrays. This will keep the codebase consistent, which is often more important than choosing one style over the other.

      One important aspect of using variables that hold arrays in TypeScript is that most of the time you will have to type them. Try the following code:

      const myArray = [];
      

      TypeScript is not able to infer the correct type expected by this array. Instead, it uses any[], which means an array of anything. This is not type-safe, and could cause confusion later in your code.

      To make your code more robust, it is recommended to be explicit about the types of the array. For example, this would make sure the array has number elements:

      const myArray: number[] = [];
      

      This way, if you try to push an invalid value to the array, TypeScript will yield an error. Try out the following code:

      const myArray: number[] = [];
      
      myArray.push('some-text');
      

      The TypeScript Compiler will show error 2345:

      Output

      Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

      Tuples

      Tuples are arrays with a specific number of elements. One common use-case for this is storing 2D coordinates in the format [x, y]. If you are working with React and using Hooks, the result from most Hooks is also a tuple, like const [isValid, setIsValid] = React.useState(false).

      To type a tuple, as opposed to when typing an array, you wrap the type of the elements inside a [], separating them with commas. Imagine you are creating a literal array with the types of the elements:

      const position: [number, number] = [1, 2];
      

      If you try to pass less, or more, than the number of elements that the tuple expects, the TypeScript Compiler is going to show the error 2322.

      Take the following code, for example:

      const position: [number, number] = [1, 2, 3];
      

      This would yield the following:

      Output

      Type '[number, number, number]' is not assignable to type '[number, number]'. Source has 3 element(s) but target allows only 2. (2322)

      any

      In certain situations it may be too hard to specify the types of a value, such as if that value is coming from a third-party library or from code that was initially written without TypeScript. This can be especially common when migrating a JavaScript codebase to TypeScript in small steps. In these scenarios, it is possible to use a special type called any, which means any type. Using any means opting-out of type checking, and is the same as making the TypeScript Compiler ignore that value.

      Take the following code block:

      let thisCanBeAnything: any = 12345;
      
      thisCanBeAnything = "I can be anything - Look, I'm a string now";
      
      thisCanBeAnything = ["Now I'm an array - This is almost like pure JavaScript!"];
      

      None of these declarations will give an error in TypeScript, since the type was declared as any.

      Note: Most of the time, if you can, you should avoid using any. Using this loses one of the main benefits of TypeScript: having statically typed code.

      unknown

      The unknown type is like a type-safe counterpart of the any type. You can use unknown when you want to type something that you can not determine the value of, but still want to make sure that any code using that value is correctly checking the type before using it. This is useful for library authors with functions in their library that may accept a broad range of values from their users and do not want to type the value explicitly.

      For example, if you have a variable called code:

      let code: unknown;
      

      Then later in the program you can assign different values to that field, like 35 (number), or completely unrelated values, like arrays or even objects.

      Note: You are using let because you are going to assign a new value to that variable.

      Later in the same code, you could set code to a number:

      code = 35;
      

      But then later you could assign it to an array:

      code = [12345];
      

      You could even re-assign it to an object:

      code = {};
      

      If later in the code you want to compare that value against some other number, like:

      const isCodeGreaterThan100 = code > 100;
      

      The TypeScript compiler is going to display the error 2571:

      Output

      Object is of type 'unknown'. (2571)

      This happens because code needs to be a number type for this comparison, not an unknown type. When doing any operation with a value of type unknown, TypeScript needs to make sure that the type is the one it expects. One example of doing this is using the typeof operator that already exists in JavaScript. Examine the following code block:

      if (typeof code === 'number') {
        const isCodeGreaterThan100 = code > 60;
        // ...
      } else {
        throw new Error('Invalid value received as code');
      }
      

      In this example, you are checking if code is a number using the typeof operator. When you do that, TypeScript is going to coerce the type of your variable to number inside that if block, because at runtime the code inside the if block is only going to be executed if code is currently set to a number. Otherwise, you will throw a JavaScript error saying that the value passed is invalid.

      To understand the differences between the unknown and any types, you can think of unknown as “I do not know the type of that value” and any as “I do not care what type this value holds”.

      void

      You can use the void type to define the variable in question as holding no type at all. If you assign the result of a function that returns no value to a variable, that variable is going to have the type void.

      Take the following code:

      function doSomething() {};
      
      const resultOfVoidFunction: void = doSomething();
      

      You will rarely have to use the void type directly in TypeScript.

      null and undefined

      null and undefined values in TypeScript have their own unique types that are called by the same name:

      const someNullField: null = null;
      const someUndefinedField: undefined = undefined;
      

      These are especially useful when creating your own custom types, which will be covered later in this series.

      never

      The never type is the type of a value that will never exist. For example, imagine you create a numeric variable:

      const year: number = 2021;
      

      If you create an if block to run some code if year is not a number, it could be like the following:

      if (typeof year !== "number") {
        year;
      }
      

      The type of the variable year inside that if block is going to be never. This is because, since year is typed as number, the condition for this if block will never be met. You can think of the never type as an impossible type because that variable can’t have a value at this point.

      object

      The object type represents any type that is not a primitive type. This means that it is not one of the following types:

      • number
      • string
      • boolean
      • bigint
      • symbol
      • null
      • undefined

      The object type is commonly used to describe object literals because any object literal can be assigned to it:

      const programmingLanguage: object = {
        name: "TypeScript"
      };
      

      Note: There is a much better type than object that could be used in this case called Record. This has to do with creating custom types and is covered in a later tutorial in this series.

      Conclusion

      In this tutorial, you tried out the different basic types that are available in TypeScript. These types are going to be frequently used when working in a TypeScript codebase and are the main building blocks to create more complex, custom types.

      For more tutorials on TypeScript, check out our TypeScript Topic page.



      Source link