Common types of TypeScript

The official documents of TypeScript have been updated, but the Chinese documents I can find are still in older versions. Therefore, some chapters which are newly added and revised are translated.

This translation was compiled from TypeScript Handbook. Everyday Types "Chapter.

This article is not translated strictly according to the original text, and some of the contents are also explained and supplemented.

Common Types (Everyday Types)

This chapter explains some of the most common types in JavaScript and how they are described. Note that this chapter is not exhaustive, and subsequent chapters will explain more ways to name and use types.

Types can appear in many places, not just in type annotations. We need to learn not only the types themselves, but also where to use them to create new structures.

Let's review the most basic and common types that form the basis for building more complex types.

Original types: string, number, and boolean (The primitives)

JavaScript has three very common Primitive type : string, number and boolean, each type has a corresponding type in TypeScript. Their names are the same as what you get when you use the typeof operator in JavaScript.

  • String represents a string, such as "Hello, world"
  • Number denotes a number, such as 42. There is no int or float in JavaScript. All numbers are of type number
  • boolean represents a boolean value, which is actually two values: true and false.
    ‚Äč

    Type names String, Number, and Boolean (capital letters) are also legal, but they are some very rare special built-in types. So types always use string, numberor boolean.

Array

Declaring an array type similar to [1, 2, 3] requires the syntax number []. This syntax can be applied to any type (for example, string[] represents an array of strings). You may also see the same writing for Array<number>. We will introduce T<U>grammar in the Generics section.

Note that [number] and number [] mean different things. tuple chapter

any

TypeScript has a special type, any, which can be set to any if you don't want a value to cause type checking errors.

When a value is of any type, you can either get any of its properties (which will also be converted to any type), call it like a function, assign it to any type of value, assign any type of value to it, or do other grammatically correct things:

let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed 
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

any types are useful when you don't want to write a long type of code and just want TypeScript to know that a particular piece of code is okay.

noImplicitAny

If you do not specify a type, TypeScript cannot infer its type from the context, and the compiler will default to any type.

If you always want to avoid this situation, after all, TypeScript does not check any type, you can turn on compiled items noImplicitAny TypeScript will error when implicitly inferred to any.

Type annotations on variables (Type Annotations on Variables)

When you declare a variable with const, var, or let, you can optionally add a type annotation that explicitly specifies the type of the variable:

let myName: string = "Alice";

TypeScript does not use the form of a type declaration on the left, such as int x = 0; Type annotations tend to follow the content of the type to be declared.

But most of the time, this is not necessary. Because TypeScript automatically infers the type. For example, the type of variable can be inferred from the initial value:

// No type annotation needed -- 'myName' inferred as type 'string'
let myName = "Alice";

Most of the time, you don't need to learn rules to infer. If you're just getting started, try using as few type notes as possible. You may be surprised that TypeScript only needs a small amount of content to fully understand what is going to happen.

Function

Functions are the primary way JavaScript passes data. TypeScript allows you to specify the type of input and output values for a function.

Parameter Type Annotations

When you declare a function, you can add a type comment after each parameter to indicate what type of parameters the function can accept. The parameter type comment follows the parameter name:

// Parameter type annotation
function greet(name: string) {
  console.log("Hello, " + name.toUpperCase() + "!!");
}

When a parameter has a type comment, TypeScript checks the arguments of the function:

// Would be a runtime error if executed!
greet(42);
// Argument of type 'number' is not assignable to parameter of type 'string'.

Even if you don't type the parameter, TypeScript checks to see if the number of parameters passed in is correct

Return Type Annotations

You can also add type annotations for return values. The type comment for the return value follows the parameter list:

function getFavoriteNumber(): number {
  return 26;
}

As with variable type annotations, you don't always need to add a return type annotation; TypeScript infers the return type of a function based on its return statement. In this example, type annotations are the same as no type, but some code libraries explicitly specify the type of return value, either because documentation is required, or because accidental modifications are prevented, or simply because of personal preferences.

Anonymous Functions

Anonymous functions differ a little from function declarations in that when TypeScript knows how an anonymous function will be called, its parameters are automatically assigned a type.

This is an example:

// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
 
// Contextual typing for function
names.forEach(function (s) {
  console.log(s.toUppercase());
  // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
 
// Contextual typing also applies to arrow functions
names.forEach((s) => {
  console.log(s.toUppercase());
  // Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

Although parameter s does not add a type annotation, TypeScript infers the type of S based on the type of forEach function and the type of array passed in.

This process is called contextual typing, because it is from the context in which the function appears that it should have a type.

As with inference rules, you don't need to learn how it happens, as long as you know it does exist and helps you eliminate some unnecessary annotations. Later, we'll see more examples of how the context in which a value appears affects its type.

Object Types

In addition to the original type, the most common type is the object type. To define an object type, we simply list its properties and corresponding types.

For instance:

// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

Here, we add a type to the parameter, which has two attributes, x and y, both of which are number ed. You can use it, or; Separate attributes, and the separator for the last attribute is OK with or without.

The type corresponding to each property is optional, and if you do not specify it, the any type is used by default.

Optional Properties

Object types can specify some or even all of the attributes as optional. You only need to add one after the attribute name?:

function printName(obj: { first: string; last?: string }) {
  // ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

In JavaScript, if you get a property that does not exist, you get an undefined instead of a runtime error. So when you get an optional attribute, you need to check if it is undefined before you use it.

function printName(obj: { first: string; last?: string }) {
  // Error - might crash if 'obj.last' wasn't provided!
  console.log(obj.last.toUpperCase());
  // Object is possibly 'undefined'.
  if (obj.last !== undefined) {
    // OK
    console.log(obj.last.toUpperCase());
  }
 
  // A safe alternative using modern JavaScript syntax:
  console.log(obj.last?.toUpperCase());
}

Union Types

The TypeScript type system allows you to build new types based on existing types using a series of operators. Now that we know how to write some basic types, it's time to put them together.

Defining a Union Type

The first type of combination uses a union type, a union type consisting of two or more types, indicating that the value may be any of these types. Each of these types is a member of a union type.

Let's write a function that handles strings or numbers:

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
// Type '{ myID: number; }' is not assignable to type 'number'.

Use Joint Types

It is easy to provide a value that matches a union type; you only need to provide a value that matches any one of the Union member types. So how do you use a union-type value when you have one?

What TypeScript asks you to do must be valid for each member of the union. For example, if you have a union type string | number, you cannot use a method that only exists on strings:

function printId(id: number | string) {
  console.log(id.toUpperCase());
    // Property 'toUpperCase' does not exist on type 'string | number'.
    // Property 'toUpperCase' does not exist on type 'number'.
}

The solution is to narrow down the federated types with code, as if you were using them in JavaScript without type annotations. Type narrowing occurs when TypeScript infers a more specific type from the structure of the code.

For example, TypeScript knows that using typeof on a value of type string returns the string "string":

function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}

For another example, use functions such as Array.isArray:

function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {
    // Here: 'x' is 'string[]'
    console.log("Hello, " + x.join(" and "));
  } else {
    // Here: 'x' is 'string'
    console.log("Welcome lone traveler " + x);
  }
}

Note that in the else branch, we don't need to do anything special. If x is not a string[], then it must be a string.

Sometimes, if each member of a union type has an attribute, for example, a slice method for numbers and strings, you can use that attribute directly instead of narrowing the type:

// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}

You may be wondering why joint types can only use the intersection of these types of attributes. Let's take an example. There are now two rooms, one with eight-foot-tall people wearing hats and the other with Spanish-speaking people wearing hats. When these two rooms are combined, the only thing we know is that everyone wears hats.

TypeScript Series

  1. Getting Started with TypeScript
  2. Type Narrowing for TypeScript
  3. Functions of TypeScript
  4. Object Type of TypeScript
  5. Generics of TypeScript
  6. Keyof Operator for TypeScript
  7. Typeof operator for TypeScript
  8. Index Access Type for TypeScript
  9. TypeScript Conditional Type

WeChat:'mqyqingfeng', which adds me to Kuiyu's unique audience.

If there are any errors or inaccuracies, be sure to correct them. Thank you very much. If you like it or are inspired by it, welcome star and encourage the author.

Tags: Javascript Front-end ECMAScript html5 TypeScript

Posted on Wed, 01 Dec 2021 23:10:35 -0500 by wrathican