• Note: There are currently no Chinese translations of the latest TypeScript official documentation available online, so there is a translation plan. Since I’m also a beginner to TypeScript, I can’t guarantee 100% accuracy in TypeScript translations, so I feel free to point out any errors in the comments section.
  • Translation content: The temporary translation content is TypeScript Handbook, and other parts of the translation document will be supplemented later.
  • Project address: typescript-doc-zh, if it helps you, you can click a star ~

The official documentation address of this chapter is: Everyday Types

Common type

In this chapter, we’ll cover some of the most common data types in JavaScript code and explain how they are described in TypeScript. This section does not cover all types in detail, and we will cover more ways to name and use other types in subsequent chapters.

Types can appear in many other places as well as in type annotations. As well as learning about the types themselves, we will also learn how to use them in some places to form new structures.

First, let’s review the most basic and common types for writing JavaScript or TypeScript code. They will later become core components of more complex types.

Original type:string,numberboolean

JavaScript has three very common primitive types: string, number, and Boolean. Each type has a TypeScript counterpart. As you might expect, they have the same names as strings obtained using JavaScript’s Typeof operator:

  • stringSaid a similar"Hello, world!"Such a string value
  • numberSaid a similar42That’s a number like that. JavaScript has no special runtime value for integers, so it doesn’tintorfloatType – All numbers arenumbertype
  • booleanRepresents a Boolean valuetruefalse

The type names String, Number, and Boolean (beginning with an uppercase letter) are also legal, but they refer to built-in types that rarely appear in code. Always use String, number, and Boolean

An array of

To represent array types like [1,2,3], you can use the syntax number[]. This syntax can also be used for any type (such as string[] to indicate that array elements are strings). Another way to write it is Array

, which has the same effect. We’ll cover the T
syntax in more detail when we cover generics.

Note that [number] is a tuple, unlike a normal array

any

TypeScript also has a special any type. Use any when you don’t want a value to cause type checking errors.

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

let obj: any = { x: 0 };
// All of the following code does not cause compilation errors. Using any will ignore type checking and assume that
// You understand the current environment better than TypeScript
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
Copy the code

Any is useful when you don’t want to write a long list of types to convince TypeScript that a line of code is okay.

noImplicitAny

When you don’t explicitly specify a type and TypeScript can’t infer type from the context, the compiler treats it as any by default.

However, you usually avoid this because any bypasses type checking. Enable the noImplicitAny configuration item to flag any implicitly inferred as an error.

Type annotations for variables

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';
Copy the code

TypeScript doesn’t declare type on the left side of expressions like int x = 0. A type annotation always follows the thing to declare the type.

However, in most cases, annotations are not required. TypeScript automates type inference 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 a type annotation -- myName is automatically inferred to be a string
let myName = 'Alice';
Copy the code

In most cases, you don’t need to learn the rules of type inference. If you’re a beginner, try to use type annotations as sparingly as possible — you might be surprised at how few annotations TypeScript needs to fully understand what’s happening.

function

Functions are the primary 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 argument to declare what types of arguments the function can accept. The parameter’s type annotation follows each parameter’s name:

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

When a function argument has a type annotation, TypeScript checks the type of arguments 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'.
Copy the code

Even if you don’t add type annotations to arguments, TypeScript checks that you passed the correct number of arguments

Return value type annotation

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

function getFavourNumber() :number {
    return 26;
}
Copy the code

As with variable type annotations, we generally don’t need to add a type annotation to a return value because TypeScript infer the type of a function’s return value based on the return statement. The type annotations in the example above do not change anything. Some code libraries explicitly specify the type of the return value, either for documentation purposes, to prevent unexpected changes, or simply for personal preference.

Anonymous functions

Anonymous functions are a little different from function declarations. When a function appears somewhere and TypeScript can infer how it was called, the function’s parameters are automatically typed.

Such as:

There are no type annotations, but TypeScript can still find bugs in subsequent code
const names = ["Alice"."Bob"."Eve"];
 
// Infer the types 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'?
});
 
// The arrow function can also be inferred correctly
names.forEach((s) = > {
  console.log(s.toUppercase()); ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^//Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
Copy the code

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

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

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

Object type

After primitive types, the most common types are object types. It refers to any JavaScript value that contains an attribute. To define an object type, simply list its attributes and types.

For example, here is a function that takes an object type as an argument:

// 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 });
Copy the code

Here, the type annotation we add for the parameter is an object containing the x and y attributes (both of type number). You can use, or; Separate each attribute, the last attribute can be delimited with or without the delimiter.

The type portion of each attribute is also optional; if you do not specify a type, it will take any.

Optional attribute

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

function printName(obj: { first: string; last? :string }) {
  // ...
}
// It can be written either way
printName({ first: "Bob" });
printName({ first: "Alice".last: "Alisson" });
Copy the code

In JavaScript, if you access a property that doesn’t exist, you’ll get undefined instead of a runtime error. Therefore, when you read an optional property, 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 write using modern JavaScript syntax:
  console.log(obj.last? .toUpperCase()); }Copy the code

The joint type

TypeScript’s type system allows you to create new types based on existing types using a number of operators. Now that we know how to write the 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 and represents a value that can be taken from any one of these types. Each type is called a member of the 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");
/ / an 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'.
Copy the code

Using union types

Providing a value that matches a union type is 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 limits the actions you can take on a union type and only takes effect if that action applies to every member of the union type. For example, if you have a joint type string | number, then you will not be able to use only by the string the method called:

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'.
}
Copy the code

The solution is to narrow the union types in your code, just as JavaScript does without type annotations. Narrowing occurs when TypeScript is able to infer a more specific type based on the structure of the code.

For example, TypeScript knows that only string values return “string” when typeof is used:

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

Another example is using a function like array. isArray:

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

Note that in the else branch, we do not need to make additional judgments — if x is not a string[], it must be a string.

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

/ / the return value can be concluded that as the number [] | string
function getFirstThree(x: number[] | string) {
  return x.slice(0.3);
}
Copy the code

The intersection of the attributes of the various types of the union type can be a little confusing. In fact, it’s not surprising that the term “union” comes from type theory. The joint type number | string is composed of the value of each type of joint. Given two sets and their corresponding facts, only the intersection of the facts can be applied to the intersection of the set itself. For example, if the people in one room are all tall and wearing hats, and the people in the other room are All Spanish and wearing hats, then the only fact we can get from the two rooms is that everyone must be wearing a hat.

Type the 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 for 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 });
Copy the code

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

type ID = number | string;
Copy the code

Note that aliases are just aliases — you cannot use type aliases to create different “versions” of the same type. When you use an alias, it works just as if you were writing the actual type. In other words, the code looks illegal, but in TypeScript this is fine, both the alias and the actual type refer 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 a new string to it
userInput = "new input";
Copy the code

interface

Interface declarations are another way of naming 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 });
Copy the code

Just like using type aliases above, this example works just as well as using an anonymous object type directly. TypeScript only cares about the structure of the value we pass to printCoord — it only cares if the value has the desired properties. It is this focus on type structure and power that makes TypeScript a structural, typed type system.

The difference between type aliases and interfaces

Type aliases are similar to interfaces, and in most cases you can use either of them. Almost all features of an interface can be used in a type alias. The key difference is that the type alias cannot be “opened” again to add new attributes, whereas the interface can always be extended.

// 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 extended through intersection
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

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

// Add new attributes to an existing interface
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 changed
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

 // Error: Duplicate identifier 'Window'
Copy the code

You’ll learn more about this in later chapters, so don’t worry if you don’t understand it yet.

  • Prior to TypeScript version 4.2, the name of a type alias may appear in error messages, sometimes in place of an equivalent anonymous type (which may or may not be required). The name of the interface always appears in the error message
  • Type aliases cannot be declarative merge, but interfaces can
  • Interfaces can only be used to declare shapes of objects, not names of primitive types
  • Interface names will always appear in their original form in the error message, but only when they are used as names

In most cases, you can choose to use one or the other as you like, and TypeScript will tell you if it needs to use a different declaration. If you like heuristics, you can use interfaces and then use type aliases when you need to use other features.

Types of assertions

Sometimes you know a value’s type better than TypeScript does.

For example, if you use document.getelementById, TypeScript only knows that the call will return an HTMLElement, but you know that your page will always have an HTMLCanvasElement 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;
Copy the code

As with 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");
Copy the code

Remember: Because type assertions are removed at compile time, there are no runtime checks related to type assertions. Even if the type assertion is wrong, no exception is thrown or NULL is generated

TypeScript only allows a type after an assertion to be more or less specific than the type before it. This rule prevents “impossible” casts like the following:

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"
Copy the code

Sometimes, this rule can be too conservative, preventing us from doing more complex and efficient transformations. If so, a two-step assertion can be used, first to assert any (or unknown, more on that later) and then to assert the desired type:

const a = (expr as any) as T;
Copy the code

Literal type

In addition to the generic string and number types, we can also think of concrete strings or numbers as a type.

How do you understand that? 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 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 systemchangingString; ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^// let changingString: string
      
let changingString: string
 
const constantString = "Hello World";
Because constantString can only represent one possible string, it has one
// The representation of the literal typeconstantString; ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^// const constantString: "Hello World"
      
Copy the code

Used alone, the literal type is not very useful:

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

In the example above, the variable has only one possible value, which makes no sense!

But by combining literal types into union types, you can represent 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"'.
Copy the code

The same goes for numeric literal types:

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

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"'.
Copy the code

There is another type of literal: Boolean literals. There are only two types of Boolean literals, true and false. Joint type Boolean type itself is actually true | false an alias.

Literal inference

When you initialize a variable into an object, TypeScript assumes that the object’s properties will change later. For example:

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

TypeScript does not consider it an error to assign a value of 1 to an attribute that previously had a value of 0. Another way to think about it is that obj.counter must be of type number, not 0, because types can be used to determine read and write 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"'.
Copy the code

(translator note: the handleRequest signature here for (url: string, method: “GET” | “POST”) = > void)

In the example above, req.method is inferred to be a string, not “GET”. Because other code might be executed between creating req and calling handleRequest, req.method might be assigned a string like “GUESS”, so TypeScript considers such code to be faulty.

There are two ways to solve this problem:

  1. Change the inferred result of a type by adding a type assertion:

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

Method 1 means “I intentionally kept the req.method literal “GET” to prevent it from being assigned to any other string later; Method two says “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 a literal type:

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

    The as const suffix has a similar effect to const, but is used in the type system. It ensures that all attributes of an object are given a literal type, rather than more generic types such as String or number.

nullundefined

JavaScript has two raw values for missing or uninitialized values: null and undefined.

TypeScript also has two types with the same name. Their behavior depends on whether or not you have strictNullChecks enabled.

disablestrictNullChecks

After strictNullChecks is disabled, you can still access values that may be null and undefined, which can also be assigned to either type. This behavior is similar to that of languages that lack null-checking, such as C# and Java. The lack of checking for these values can be a source of a lot of bugs, and we recommend that developers always enable strictNullChecks whenever possible.

To enable thestrictNullChecks

With strictNullChecks enabled, when a value is null or undefined, you need to check it first before using methods or attributes with that value. Just like checking if an optional attribute is undefined before using it, we can use type narrowing to check if a value can be null:

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

Non-null-value assertion operator (!The suffix)

TypeScript also provides a special syntax that excludes null and undefined from types without explicit checking. Add a suffix to any expression! , 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()); }Copy the code

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

The enumeration

Enumerations are a feature TypeScript has added to JavaScript. It allows you to describe a value that can be one of a possible set of 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 be aware that this feature exists, but unless you are sure, you may want to postpone using it. You can learn more about enumerations on the enumeration references page.

Other less common primitive types

It’s worth noting that JavaScript’s other primitive types also have representations in the type system. But we won’t go into that here.

BigInt

ES2020 introduced BigInt to represent very large integers in JavaScript:

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

You can learn more about BigInt in TypeScript 3.2 publishing logs.

symbol

In JavaScript, we can create a globally unique reference using 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.
}
Copy the code

You can learn more about this on the Symbol reference page.