TypeScript official manual translation plan [2]: common type

  • Note: at present, there is no Chinese translation of the latest official documents of TypeScript on the Internet, so there is such a translation plan. Because I am also a beginner of TypeScript, I can't guarantee the 100% accuracy of translation. If there are errors, please point them out in the comment area;
  • Translation content: the tentative translation content is TypeScript Handbook , other parts of the translated documents will be supplemented when available;
  • Project address: TypeScript-Doc-Zh , if it helps you, you can click a star~

Official document address of this chapter: Everyday Types

Common type

In this chapter, we will cover some of the most common data types in JavaScript code, and explain how these types are described in TypeScript. This chapter will not cover all types in detail. In subsequent chapters, we will introduce more methods of naming and using other types.

Types can appear not only in type annotations, but also in many other places. While learning the types themselves, we will also learn how to use these types to form new structures in some places.

First, let's review the most basic and commonly used types when writing JavaScript or TypeScript code. They will later become a core component of more complex types.

Primitive types: string, number, and boolean

There are three common types of JavaScript Original type : string, number, and boolean. Each type has a corresponding type in TypeScript. As you might expect, their names are the same as the strings obtained by using JavaScript's typeof operator:

  • String represents a string value like "Hello, world!"
  • Number represents a number like 42. For integers, JavaScript has no special runtime value, so there is no int or float type -- all numbers are of type number
  • boolean indicates boolean values true and false

The type names string, number, and Boolean (starting with uppercase letters) are also legal, but they refer to built-in types that rarely appear in code. Always use string, number, and Boolean

array

To represent array types like [1,2,3], you can use the syntax number []. This syntax can also be used for any type (for example, string [] indicates that the array elements are of string type). Another way to write it is array < number >, which has the same effect. When we explain generics later, we will introduce T < U > syntax in detail.

Note that [number] is different from an ordinary array. It represents tuple

any

TypeScript also has a special any type. Use any when you don't want a value to cause a type check error.

When a value is of any type, you can access any of its properties (these properties will also be of any type), call it as a function, assign it to any type of value (or assign any type of value to it), or any syntactically compliant operation:

let obj: any = { x: 0 };
// None of the following code will cause compilation errors. Using any ignores type checking and assumes that
// You know the current environment better than TypeScript
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

any type works well when you don't want to write a long list of types to convince TypeScript that a line of code is OK.

noImplicitAny

When you do not explicitly specify a type and TypeScript cannot infer the type from the context, the compiler will treat it as any type by default.

However, you usually avoid this because any bypasses type checking. Enable noImplicitAny The configuration item can mark any implicitly inferred any as an error.

Type annotation of variable

When you declare a variable using const, var or let, you can optionally add a type annotation to explicitly specify the type of the variable:

let myName: string = 'Alice';

TypeScript does not adopt the style of "declaring type on the left of expression" like int x = 0. Type annotations always follow what is to be declared.

However, in most cases, annotations are not required. TypeScript will automatically infer types in your code as much as possible. For example, the type of a variable is inferred based on its initial value:

// There is no need to add type annotation - myName will be automatically inferred as string type
let myName = 'Alice';

In most cases, you don't need to learn the rules of type inference. If you are still a beginner, try to use type annotations as little as possible - you may be surprised to find that TypeScript needs so few annotations to fully understand what is happening.

function

Functions are the main way to pass data in JavaScript. TypeScript allows you to specify the input and output types of functions.

Parameter type annotation

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

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

When a parameter of a function has a type annotation, TypeScript will check the type of the argument passed to the function:

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

Even if no type annotation is added to the parameter, TypeScript will check whether the number of parameters you pass is correct

Return value type annotation

You can also add type annotations to the return value. The return value type annotation appears after the parameter list:

function getFavourNumber(): number {
    return 26;
}

Like the type annotation of variables, we usually do not need to add a type annotation to the return value, because TypeScript infers the type of the function return value based on the return statement. The type annotation in the above example doesn't change anything. Some code bases explicitly specify the type of return value, which may be required for documentation, to prevent accidental modification, or just personal preference.

Anonymous function

Anonymous functions are somewhat different from function declarations. When a function appears somewhere and TypeScript can infer how it is called, the parameters of the function will be automatically assigned types.

For example:

// There are no type annotations here, but TypeScript can still find bug s in subsequent code
const names = ["Alice", "Bob", "Eve"];
 
// Inferring the type of anonymous function parameters based on context
names.forEach(function (s) {
  console.log(s.toUppercase());
                 ^^^^^^^^^^^^  
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
 
// For the arrow function, it can also be inferred correctly
names.forEach((s) => {
  console.log(s.toUppercase());
               ^^^^^^^^^^^^^
//Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

Even if there is no type annotation for parameter s, TypeScript can determine the type of S based on the type of forEach function and the inference of name array type.

This process is called context type inference, because the context in which the function is called determines the type of its parameters.

Similar to inference rules, you don't need to learn how this process happens, but after you know that this process will happen, you will naturally know when you don't need to add type annotations. We'll see more examples later to see how the context of a value affects its type.

object type

In addition to the original type, the most common type is the object type. It refers to any JavaScript value that contains attributes. To define an object type, simply list its properties and types.

For example, the following is a function that accepts an object type as a parameter:

// The type annotation of a parameter 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, the type annotation we added for the parameter is an object containing both x and y attributes (both of which are of type number). You can use, or; Separate each attribute, and the separator of the last attribute can be added or not.

The type part of each attribute is also optional. If you do not specify a type, it will adopt any type.

optional attribute

Object types can also specify that some or all properties are optional. You only need to add one after the corresponding property name? You can:

function printName(obj: { first: string; last?: string }) {
  // ...
}
// Either of the following is OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });

In JavaScript, if you access a non-existent property, you will get undefined instead of a runtime error. Therefore, when you read an optional attribute, you need to check whether it is undefined before using it.

function printName(obj: { first: string; last?: string }) {
  // If obj.last does not have a corresponding value, an error may be reported!
  console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
  if (obj.last !== undefined) {
    // OK
    console.log(obj.last.toUpperCase());
  }
 
  // Here is another safe way to use modern JavaScript syntax:
  console.log(obj.last?.toUpperCase());
}

Union type

TypeScript's type system allows you to create new types based on existing types using a large number of operators. Now that we know how to write basic types, it's time to start combining them in an interesting way.

Define a union type

The first way to combine types is to use union types. A union type consists of two or more types. It represents that the value of any of these types can be taken. Each type is called a member of a union type.

Let's write a function that can handle strings or numbers:

function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// report errors
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 union type

Providing a value that matches a union type is very simple -- you just need to provide a type that matches a member of the union type. If a value is a union type, how do you use it?

TypeScript will limit the actions you can take on the union type. The action will take effect only when the action takes effect for each member of the union type. For example, if you have the union type string | number, you will not be able to use methods that can only be called by string:

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 the union type in the code, which is the same as that of JavaScript without type annotation. Narrowing occurs when TypeScript can infer a more specific type based on the code structure.

For example, TypeScript knows that only values of type string will return "string" after using typeof:

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

Another example is to use functions like 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 a string
    console.log("Welcome lone traveler " + x);
  }
}

Note that in the else branch, we don't need to make any additional judgment -- if x is not string [], it must be string.

Sometimes, all members of a federation type may have something in common. For example, both arrays and strings have slice methods. If each member of a union type has a public attribute, you can directly use this attribute without narrowing it:

// The return value will be inferred as number[] | string
function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}

You may feel a little confused about the intersection of the attributes of various types of union types. In fact, this is not surprising. The term "union" comes from type theory. The union type number | string consists of the union of the values of each type. Given two sets and their corresponding facts, only the intersection of facts can be applied to the intersection itself. For example, people in one room are tall and wear hats, and people in the other room are Spanish and wear hats. Then when people in the two rooms are put together, the only fact we can get is that everyone must wear hats.

Type alias

So far, we have used object types or union types directly in type annotations. This is convenient, but usually we prefer to refer to a type multiple times by a single name.

A type alias is used for this -- it can be used as a name to refer to any type. The syntax of a type alias is as follows:

type Point = {
  x: number;
  y: number;
};
 
// The effect is exactly the same as the previous example
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

Not just object types, you can use type aliases for any type. For example, you can name a union type:

type ID = number | string;

Note that aliases are just aliases - you can't use type aliases to create different "versions" of the same type. When you use aliases, the effect is the same as when you write the actual type directly. In other words, the code looks illegal, but this is no problem in TypeScript. Both alias and actual type point to the same type:

type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}
 
// Create an input
let userInput = sanitizeInput(getInput());
 
// You can assign it a string again
userInput = "new input";

Interface

Interface declaration is another way to name object types:

interface Point {
  x: number;
  y: number;
}
 
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });

Just like using the type alias above, this example can work normally, and its effect is the same as using an anonymous object type directly. TypeScript only cares about the structure of the value we pass to printCoord -- it only cares about whether the value has the desired attribute. It is precisely because of this feature that only focuses on the structure and capability of types, so we say that TypeScript is a structural and typed type system.

Differences between type aliases and interfaces

Type aliases are very similar to interfaces. In most cases, you can choose any one to use. Almost all features of an interface can be used in type aliases. The key difference between the two is that type aliases cannot be "opened" again and new properties can be added, and interfaces can always be expanded.

// The interface can be expanded freely
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear = getBear() 
bear.name
bear.honey

// Type aliases need to be expanded through intersection
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

const bear = getBear();
bear.name;
bear.honey;

// Add new properties to existing interfaces
interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

const src = 'const a = "Hello World"';
window.ts.transpileModule(src, {});

// Once a type alias is created, it cannot be modified
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

 // Error: Duplicate identifier 'Window'

You will learn more about this in later chapters, so it doesn't matter if you don't understand it yet.

  • Before typescript version 4.2, the name of the type alias may appear in the error message and sometimes replace the equivalent anonymous type (may or may not be required). The name of the interface always appears in the error message
  • Type alias cannot be changed Declare merge, but interfaces can
  • Interface can only be used for The shape of the declared object cannot be named for the original type
  • In the error message, the names of interfaces will always appear in the original form, but only when they are used as names

In most cases, you can choose one of them according to your personal preference, and TypeScript will also tell you whether it needs to use another declaration. If you like heuristics, you can use interfaces and wait until you need to use other features before using type aliases.

Type Asserts

Sometimes you know the type of a value better than TypeScript.

For example, if you use document.getElementById, TypeScript only knows that this call will return an HTMLElement, but you know that your page always has an htmlcanvas element with a given ID.

In this case, you can use type assertions to specify a more specific type:

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

Like type annotations, the compiler eventually removes type assertions to ensure that they do not affect the runtime behavior of the code.

You can also use the equivalent angle bracket syntax (provided the code is not in a. tsx file):

const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

Remember: because type assertions are removed during compilation, there are no run-time checks related to type assertions. Even if the type assertion is wrong, no exception is thrown or null is generated

TypeScript only allows types after assertions to be more specific or less specific than previous types. This rule can prevent the following "impossible" cast types:

const x = "hello" as number;
// The conversion from type 'string' to type 'number' may be wrong because the two types do not overlap sufficiently. If this is intentional, first convert the expression to "unknown"

Sometimes, this rule may be too conservative, which will prevent us from carrying out more complex and effective conversion operations. If so, you can use two-step assertions, first asserting as any (or unknown, which will be described later), and then asserting as the desired type:

const a = (expr as any) as T;

Literal type

In addition to the common string and number types, we can also regard a specific string or number as a type.

How to understand? In fact, we only need to consider the different ways in which JavaScript declares variables. Variables declared by var and let can be modified, but const cannot. This feature is reflected in how TypeScript creates types for literals.

let changingString = "Hello World";
changingString = "Olá Mundo";
// Because changingString can represent any possible string, this is TypeScript 
// How it is described in the type system
changingString;
^^^^^^^^^^^^^^
    // let changingString: string
      
let changingString: string
 
const constantString = "Hello World";
// Because constantString can only represent one possible string, it has a
// Representation of literal type
constantString;
^^^^^^^^^^^^^^^
    // const constantString: "Hello World"
      

When used alone, literal types are not very useful:

let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.

In the above example, the variable has only one possible value, which is meaningless!

But by combining literal types into union types, you can express a more practical concept -- for example, declaring a function that accepts only certain fixed values:

function printText(s: string, alignment: "left" | "right" | "center") {
  // ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
                        ^^^^^^        
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

The same is true for numeric literal types:

function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

Of course, union types can also contain non literal types:

interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
          ^^^^^^^^^^    
// Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.

There is also a literal type: Boolean literal. There are only two Boolean literal types, true and false. The boolean type itself is actually an alias of the union type true | false.

Literal inference

When you initialize a variable to an object, TypeScript assumes that the properties of the object may change later. For example, the following code:

const obj = { counter: 0 };
if (someCondition) {
  obj.counter = 1;
}

TypeScript does not consider it an error to assign a property with a previous value of 0 to 1. Another understanding is that obj.counter must be of type number, not 0, because the type can be used to determine the reading and writing behavior.

The same is true for Strings:

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

(Note: the handleRequest signature here is (URL: string, method: "get" | "post") = > void)

In the above example, req. Method is inferred as string instead of "GET". Because other code may be executed between creating req and calling handleRequest, req.method may be assigned a string like "GUESS", so TypeScript will think that such code is wrong.

There are two ways to solve this problem:

  1. Change the inference result of type by adding type assertion:

    // Method 1:
    const req = { url: "https://example.com", method: "GET" as "GET" };
    // Method 2:
    handleRequest(req.url, req.method as "GET");

Method 1 means "I intentionally let req.method always adopt literal type" GET ", so as to prevent subsequent assignment to other strings; Method 2 means "for some reason, I'm sure the value of req.method must be" GET ".

  1. You can also use as const to convert the entire object to literal type:

    const req = { url: "https://example.com", method: "GET" } as const;
    handleRequest(req.url, req.method);

    The as const suffix has the same effect as const, but is used in the type system. It can ensure that all properties of the object are given a literal type instead of a more general type such as string or number.

null and undefined

There are two raw values in JavaScript that represent missing or uninitialized values: null and undefined.

TypeScript correspondingly has two types with the same name. Their behavior depends on whether you enable them strictNullChecks Options.

Disable strictNullChecks

Disable strictNullChecks Option, you can still access null and undefined values normally, and these two values can also be assigned to any type. This behavior behaves much like languages that lack null checking (such as C#, Java). The lack of checking these values may be the source of a large number of bug s. We recommend that developers always enable them if feasible strictNullChecks Options.

Enable strictNullChecks

Enable strictNullChecks Option, when a value is null or undefined, you need to check it before using the method or property of the value. Just like checking whether an optional attribute is undefined before using it, we can use type narrowing to check whether a value may be null:

function doSomething(x: string | null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}

Non null assertion operator (! Suffix)

TypeScript also provides a special syntax to exclude null and undefined from types without explicit checking. Add suffix after any expression!, You can effectively assert that a value cannot be null or undefined:

function liveDangerously(x?: number | null) {
  // No error will be reported
  console.log(x!.toFixed());
}

Like other type assertions, non null value assertions will not change the runtime behavior of the code, so remember: use only when you are sure that a value cannot be null or undefined!.

enumeration

Enumeration is a feature that TypeScript adds to JavaScript. It allows you to describe a value that can be one of a set of possible named constants. Unlike most TypeScript features, enumerations are not added to JavaScript at the type level, but to the language itself and its runtime. Because of this, you should understand the existence of this feature, but unless you are sure, you may need to postpone using it. You can Enumerate reference pages Learn more about enumerations in.

Other unusual primitive types

It is worth mentioning that other primitive types of JavaScript also have corresponding representations in the type system. However, we will not discuss it in depth here.

BigInt

ES2020 introduces BigInt to represent very large integers in JavaScript:

// Create a large integer through the BigInt function
const oneHundred: bigint = BigInt(100);
 
// Creating large integers with literal syntax
const anotherHundred: bigint = 100n;

You can TypeScript 3.2 release log Learn more about BigInt in.

symbol

In JavaScript, we can create a globally unique reference through the function Symbol():

const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
// This condition will always return 'false' because the types' typeof firstName 'and' typeof secondName 'do not overlap.    
}

You can Symbol reference page Learn more about it.

Keywords: Front-end TypeScript

Added by sethcox on Sat, 20 Nov 2021 20:54:17 +0200