2
  • Description : There is currently 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 in TypeScript, I cannot guarantee that the translation will be 100% accurate. If there are errors, please point out in the comment section;
  • translation content : The tentative translation content is TypeScript Handbook , and other parts of the translation document will be added later;
  • project address : TypeScript-Doc-Zh , if it helps you, you can click a star~

The official document address of this chapter: Everyday Types

Common type

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

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 in certain places to form new structures.

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

Original type: string , number and boolean

JavaScript There are three very common 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 typeof

  • string represents a string value similar to "Hello, world!"
  • number represents a value similar to 42 For integers, JavaScript has no special runtime value, so there is no int or float type-all numbers are of type number
  • boolean represents the boolean values true and false
The type names String , Number and Boolean (beginning with a capital letter) are also legal, but they refer to built-in types that rarely appear in the code. Please always use string , number and boolean

Array

To express [1,2,3] , you can use the syntax number[] . This syntax can also be used for any type (for example, string[] means that the array elements are all string types). There is another way of writing Array<number> , the effect of the two is the same. When we explain generics later, we will introduce the T<U> grammar in detail.

Note that [number] different from an ordinary array, it represents the tuple

any

TypeScript also has a special type any When you don't want a value to cause a type check error, you can use any .

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

let obj: any = { x: 0 };
// 下面所有代码都不会引起编译错误。使用 any 将会忽略类型检查,并且假定了
// 你比 TypeScript 更了解当前环境
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

any type is useful when you don't want to write a long list of types to convince TypeScript that a certain 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 the any type by default.

However, usually you will avoid this situation, because any will bypass type checking. Enabling the noImplicitAny configuration item can mark any implicitly inferred any as an error.

Variable type annotation

When you use const , var or let declare a variable, 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 the type on the left side of the expression" int x = 0 Type annotations always follow the thing to declare the type.

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

// 不需要添加类型注解 —— myName 会被自动推断为 string 类型
let myName = 'Alice';

In most cases, you don't need to deliberately learn the rules of type inference. If you are a beginner, try to use as few type annotations 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 type of input and output of a function.

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:

// 参数类型注解
function greet(name: string){
    console.log('Hello, ' + name.toUpperCase() + '!!');
}

When a parameter of a function has type annotations, TypeScript will type-check the actual parameters passed to the function:

// 如果执行,会有一个运行时错误!
greet(42);
// Argument of type 'number' is not assignable to parameter of type 'string'.
Even if you do not add type annotations to the parameters, 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 variable type annotations, we usually don't need to add a type annotation to the return value, because TypeScript will infer the type of the function return value return The type annotations in the above example will not change anything. Some code libraries will explicitly specify the type of return value, which may be for documentation needs, or to prevent accidental modification, or just personal preference.

Anonymous function

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

for example:

// 这里没有类型注解,但 TypeScript 仍能在后续代码找出 bug
const names = ["Alice", "Bob", "Eve"];
 
// 基于上下文推断匿名函数参数的类型
names.forEach(function (s) {
  console.log(s.toUppercase());
                 ^^^^^^^^^^^^  
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
 
// 对于箭头函数,也可以正确推断
names.forEach((s) => {
  console.log(s.toUppercase());
               ^^^^^^^^^^^^^
//Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});

Even if there is no s , TypeScript can forEach the type of 061a1e58b3e26c based on the type of the function name array type of s .

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

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

Object type

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

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

// 参数的类型注解是一个对象类型
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 x and y (both types are number ). You can use , or ; separate each attribute, and the separator for the last attribute can be added or not.

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

Optional attributes

The object type can also specify that some or all attributes are optional. You only need to add a ? after the corresponding attribute name:

function printName(obj: { first: string; last?: string }) {
  // ...
}
// 下面两种写法都行
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 }) {
  // 如果 obj.last 没有对应的值,可能会报错!
  console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
  if (obj.last !== undefined) {
    // OK
    console.log(obj.last.toUpperCase());
  }
 
  // 下面是使用现代 JavaScript 语法的另一种安全写法:
  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. The union type consists of two or more types, and it represents the value that can take 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");
// 报错
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 the union type is very simple-just 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 operations you can take on the union type, and the operation will only take effect when the operation is effective for each member of the union type. For example, if you have the union type string | number , then you will not be able to use methods 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 JavaScript without type annotations. Narrowing occurs when TypeScript can infer a more specific type based on the code structure.

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

function printId(id: number | string) {
  if (typeof id === "string") {
    // 在这个分支中,id 的类型是 string
    console.log(id.toUpperCase());
  } else {
    // 这里,id 的类型是 number
    console.log(id);
  }
}

Another example is to use a function Array.isArray

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

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

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

// 返回值会被推断为 number[] | string
function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}
The attributes of the various types of the union type overlap, and you may feel a little confused. In fact, this is not surprising, the term "union" comes from type theory. The union type number | string is composed of the union of the values ​​of each type. Assuming that two sets and their corresponding facts are given, only the intersection of facts can be applied to the intersection of the sets itself. For example, if the people in one room are tall and they wear hats, and the people in the other room are Spanish and also wear hats, then the people in the two rooms are put together, and the only fact we can get is: Everyone must wear a hat.

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.

The type alias is used to do 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;
};
 
// 效果和之前的例子完全一样
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 });

More than object types, you can use type aliases for any type. For example, you can name the union type:

type ID = number | string;

Note that aliases are just aliases-you cannot use type aliases to create different "versions" of the same type. When you use an alias, the effect is the same as if you directly wrote the actual type. In other words, the code looks illegal, but in TypeScript it is okay. Both the alias and the actual type refer to the same type:

type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {
  return sanitize(str);
}
 
// 创建一个输入
let userInput = sanitizeInput(getInput());
 
// 可以重新给它赋值一个字符串
userInput = "new input";

interface

Interface declaration is 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 });

Just like using type aliases above, this example can also run normally, and its effect is the same as using an anonymous object type directly. TypeScript only cares about printCoord -it only cares about whether the value has the desired properties. It is precisely because of this feature that only focuses on the structure and capabilities of types, so we say that TypeScript is a structured and typed type system.

The difference between type alias and interface

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

// 接口可以自由拓展
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

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

// 类型别名需要通过交集进行拓展
type Animal = {
  name: string
}

type Bear = Animal & { 
  honey: boolean 
}

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

// 向既有的接口添加新的属性
interface Window {
  title: string
}

interface Window {
  ts: TypeScriptAPI
}

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

// 类型别名一旦创建,就不能再修改了
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

 // Error: Duplicate identifier 'Window'

In later chapters, you will learn more about this, so it doesn’t matter if you don’t understand it yet.

  • Before TypeScript 4.2, the name of the type alias may appear in the error message, sometimes replacing the equivalent anonymous type (may or may not be needed). The name of the interface always appears in the error message
  • Type alias cannot be declaration, but the interface can be
  • The interface can only be used to , and cannot name the original type
  • In the error message, the name of the interface 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 method. If you like heuristics, you can use interfaces, and then use type aliases when you need to use other features.

Type assertion

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 a certain HTMLElement , but you know that your page always has a HTMLCanvasElement 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;

Just like type annotations, the compiler will eventually remove the type assertion to ensure that it will not affect the runtime behavior of the code.

You can also use the equivalent angle bracket syntax (provided that 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 runtime checks related to type assertions. Even if the type assertion is wrong, it will not throw an exception or generate null

TypeScript only allows the type after the assertion to be more specific or less specific than the previous type. This rule can prevent the following "impossible" forced type conversions:

const x = "hello" as number;
// 类型 "string" 到类型 "number" 的转换可能是错误的,因为两种类型不能充分重叠。如果这是有意的,请先将表达式转换为 "unknown"

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

const a = (expr as any) as T;

Literal type

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

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

let changingString = "Hello World";
changingString = "Olá Mundo";
// 因为 changingString 可以表示任意可能的字符串,这是 TypeScript 
// 在类型系统中描述它的方式
changingString;
^^^^^^^^^^^^^^
    // let changingString: string
      
let changingString: string
 
const constantString = "Hello World";
// 因为 constantString 只能表示一种可能的字符串,所以它有一个
// 字面量类型的表示形式
constantString;
^^^^^^^^^^^^^^^
    // const constantString: "Hello World"
      

When 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"'.

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, to declare a function that only accepts 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, the union type 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 literals. There are only two types of Boolean literals, namely true and false . boolean type itself is actually an alias of the 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 feel that assigning a value of 1 to a property that was previously 0 is an error. Another perspective is that obj.counter must be of number , not 0, because the type can be used to determine the 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"'.

(Translator's Note: Here handleRequest signature (url: string, method: "GET" | "POST") => void )

In the example above, req.method is inferred to be string instead of "GET" . Because other code may be executed between the creation of req and the call of handleRequest req.method may be assigned "GUESS" , so TypeScript will consider such code to be wrong.

There are two ways to solve this problem:

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

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

Method One said, "I intend to give req.method has been using a literal type "GET" ", thereby preventing subsequent assigned to it with other strings; Method II said, "For some reason, I'm sure req.method value must be “GET” ."

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

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

as const suffix const the same effect as 061a1e58b3f1a4, but it is used in the type system. It can ensure that all attributes of the object are given a literal type, instead of using a more general type string or number

null and undefined

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

Correspondingly, TypeScript also has two types with the same names. Their behavior depends on whether you enable the strictNullChecks option.

Disable strictNullChecks

After disabling the strictNullChecks option, you can still access null and undefined . These two values ​​can also be assigned to any type. This behavior is similar to languages ​​that lack null checking (such as C#, Java). The lack of checking for these values ​​may be the source of a large number of bugs. If feasible, we recommend that developers always enable the strictNullChecks option.

Enable strictNullChecks

After enabling the strictNullChecks option, when a value is null or undefined , you need to check it before using the method or property of the value. undefined before using an optional attribute, we can use type narrowing to check whether a value is possible to be null :

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

Non-null value assertion operator ( ! suffix)

TypeScript also provides a special syntax that can null and undefined from the type without explicitly checking. Adding the suffix ! after any expression can effectively assert that a value cannot be null or undefined :

function liveDangerously(x?: number | null) {
  // 不会报错
  console.log(x!.toFixed());
}

And other types of assertions as non-null value assertion does not change the runtime behavior of the code, so remember: only you can not determine if a value is null or undefined when we come to use ! .

enumerate

Enumeration is a feature added to JavaScript by TypeScript. It allows describing a value, which 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 learn more about enumeration enumeration reference page

Other uncommon primitive types

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

BigInt

ES2020 introduced BigInt to represent very large integers in JavaScript:

// 通过 BigInt 函数创建大整数
const oneHundred: bigint = BigInt(100);
 
// 通过字面量语法创建大整数
const anotherHundred: bigint = 100n;

You can learn more about BigInt TypeScript 3.2 release log

symbol

In JavaScript, we can create a globally unique reference Symbol()

const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
// 此条件将始终返回 "false",因为类型 "typeof firstName" 和 "typeof secondName" 没有重叠。    
}

You can learn more about it on the Symbol reference page


Chor
2k 声望5.9k 粉丝