日常类型

在本章中,我们将介绍 JavaScript 代码中一些最常见的值类型,并解释在 TypeScript 中描述这些类型的相应方法。这不是一个详尽的列表,未来的章节将描述更多命名和使用其他类型的方法。

¥In this chapter, we’ll cover some of the most common types of values you’ll find in JavaScript code, and explain the corresponding ways to describe those types in TypeScript. This isn’t an exhaustive list, and future chapters will describe more ways to name and use other types.

类型也可以出现在更多的地方,而不仅仅是类型注释。当我们了解类型本身时,我们还将了解可以引用这些类型以形成新结构的地方。

¥Types can also appear in many more places than just type annotations. As we learn about the types themselves, we’ll also learn about the places where we can refer to these types to form new constructs.

我们将首先回顾你在编写 JavaScript 或 TypeScript 代码时可能遇到的最基本和最常见的类型。这些稍后将形成更复杂类型的核心构建块。

¥We’ll start by reviewing the most basic and common types you might encounter when writing JavaScript or TypeScript code. These will later form the core building blocks of more complex types.

基础类型:stringnumberboolean

¥The primitives: string, number, and boolean

JavaScript 有三个非常常用的 基础类型stringnumberboolean。每个在 TypeScript 中都有对应的类型。如你所料,如果你对这些类型的值使用 JavaScript typeof 运算符,这些名称与你看到的名称相同:

¥JavaScript has three very commonly used primitives: string, number, and boolean. Each has a corresponding type in TypeScript. As you might expect, these are the same names you’d see if you used the JavaScript typeof operator on a value of those types:

  • string 表示字符串值,如 "Hello, world"

    ¥string represents string values like "Hello, world"

  • number 代表像 42 这样的数字。JavaScript 没有特殊的整数运行时值,因此没有相当于 intfloat 的值 - 一切都只是 number

    ¥number is for numbers like 42. JavaScript does not have a special runtime value for integers, so there’s no equivalent to int or float - everything is simply number

  • boolean 代表 truefalse 这两个值

    ¥boolean is for the two values true and false

类型名称 StringNumberBoolean(以大写字母开头)是合法的,但指的是一些很少出现在代码中的特殊内置类型。始终使用 stringnumberboolean 作为类型。

¥The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.

数组

¥Arrays

要指定像 [1, 2, 3] 这样的数组类型,可以使用语法 number[];此语法适用于任何类型(例如,string[] 是一个字符串数组,等等)。你也可以看到这个写成 Array<number>,意思是一样的。当我们介绍泛型时,我们将了解更多关于语法 T<U> 的信息。

¥To specify the type of an array like [1, 2, 3], you can use the syntax number[]; this syntax works for any type (e.g. string[] is an array of strings, and so on). You may also see this written as Array<number>, which means the same thing. We’ll learn more about the syntax T<U> when we cover generics.

请注意,[number] 是另一回事;请参阅 元组 部分。

¥Note that [number] is a different thing; refer to the section on Tuples.

any

TypeScript 也有一个特殊的类型,any,当你不希望某个特定的值导致类型检查错误时,你可以使用它。

¥TypeScript also has a special type, any, that you can use whenever you don’t want a particular value to cause typechecking errors.

当一个值的类型为 any 时,你可以访问它的任何属性(这又将是 any 类型),像函数一样调用它,将它分配给(或从)任何类型的值,或者几乎任何其他东西这在语法上是合法的:

¥When a value is of type any, you can access any properties of it (which will in turn be of type any), call it like a function, assign it to (or from) a value of any type, or pretty much anything else that’s syntactically legal:

ts
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;
Try

当你不想写出一个长类型来让 TypeScript 相信特定的代码行是可以的时,any 类型很有用。

¥The any type is useful when you don’t want to write out a long type just to convince TypeScript that a particular line of code is okay.

noImplicitAny

当你没有指定类型,并且 TypeScript 不能从上下文推断它时,编译器通常会默认为 any

¥When you don’t specify a type, and TypeScript can’t infer it from context, the compiler will typically default to any.

不过,你通常希望避免这种情况,因为 any 没有经过类型检查。使用编译器标志 noImplicitAny 将任何隐式 any 标记为错误。

¥You usually want to avoid this, though, because any isn’t type-checked. Use the compiler flag noImplicitAny to flag any implicit any as an error.

变量的类型注释

¥Type Annotations on Variables

当你使用 constvarlet 声明变量时,你可以选择添加类型注释以显式指定变量的类型:

¥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:

ts
let myName: string = "Alice";
Try

TypeScript 不使用 “左边的类型” 风格的声明,例如 int x = 0; 类型注释将始终位于正在键入的内容之后。

¥TypeScript doesn’t use “types on the left”-style declarations like int x = 0; Type annotations will always go after the thing being typed.

但是,在大多数情况下,这不是必需的。TypeScript 会尽可能地尝试自动推断代码中的类型。例如,变量的类型是根据其初始化程序的类型推断的:

¥In most cases, though, this isn’t needed. Wherever possible, TypeScript tries to automatically infer the types in your code. For example, the type of a variable is inferred based on the type of its initializer:

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

在大多数情况下,你不需要明确学习推断规则。如果你刚开始,请尝试使用比你想象的更少的类型注释 - 你可能会感到惊讶,TypeScript 只需要很少的内容就可以完全理解正在发生的事情。

¥For the most part you don’t need to explicitly learn the rules of inference. If you’re starting out, try using fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully understand what’s going on.

函数

¥Functions

函数是在 JavaScript 中传递数据的主要方式。TypeScript 允许你指定函数的输入和输出值的类型。

¥Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the types of both the input and output values of functions.

参数类型注解

¥Parameter Type Annotations

声明函数时,可以在每个参数后面加上类型注解,声明函数接受哪些类型的参数。参数类型注释在参数名称之后:

¥When you declare a function, you can add type annotations after each parameter to declare what types of parameters the function accepts. Parameter type annotations go after the parameter name:

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

当参数具有类型注释时,将检查该函数的参数:

¥When a parameter has a type annotation, arguments to that function will be checked:

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

即使你的参数上没有类型注释,TypeScript 仍会检查你是否传递了正确数量的参数。

¥Even if you don’t have type annotations on your parameters, TypeScript will still check that you passed the right number of arguments.

返回类型注解

¥Return Type Annotations

你还可以添加返回类型注释。返回类型注释出现在参数列表之后:

¥You can also add return type annotations. Return type annotations appear after the parameter list:

ts
function getFavoriteNumber(): number {
return 26;
}
Try

与变量类型注解非常相似,你通常不需要返回类型注解,因为 TypeScript 会根据其 return 语句推断函数的返回类型。上面例子中的类型注解并没有改变任何东西。一些代码库将明确指定返回类型以用于文档目的,以防止意外更改,或仅出于个人喜好。

¥Much like variable type annotations, you usually don’t need a return type annotation because TypeScript will infer the function’s return type based on its return statements. The type annotation in the above example doesn’t change anything. Some codebases will explicitly specify a return type for documentation purposes, to prevent accidental changes, or just for personal preference.

返回 Promise 的函数

¥Functions Which Return Promises

如果你想注释一个返回 Promise 的函数的返回类型,你应该使用 Promise 类型:

¥If you want to annotate the return type of a function which returns a promise, you should use the Promise type:

ts
async function getFavoriteNumber(): Promise<number> {
return 26;
}
Try

匿名函数

¥Anonymous Functions

匿名函数与函数声明有点不同。当一个函数出现在 TypeScript 可以确定如何调用它的地方时,该函数的参数会自动被赋予类型。

¥Anonymous functions are a little bit different from function declarations. When a function appears in a place where TypeScript can determine how it’s going to be called, the parameters of that function are automatically given types.

这是一个例子:

¥Here’s an example:

ts
const names = ["Alice", "Bob", "Eve"];
 
// Contextual typing for function - parameter s inferred to have type string
names.forEach(function (s) {
console.log(s.toUpperCase());
});
 
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUpperCase());
});
Try

即使参数 s 没有类型注释,TypeScript 还是使用 forEach 函数的类型以及推断的数组类型来确定 s 将具有的类型。

¥Even though the parameter s didn’t have a type annotation, TypeScript used the types of the forEach function, along with the inferred type of the array, to determine the type s will have.

这个过程称为上下文类型,因为函数发生的上下文告知它应该具有什么类型。

¥This process is called contextual typing because the context that the function occurred within informs what type it should have.

与推断规则类似,你不需要明确了解这是如何发生的,但了解它确实发生可以帮助你注意到何时不需要类型注释。稍后,我们将看到更多关于值出现的上下文如何影响其类型的示例。

¥Similar to the inference rules, you don’t need to explicitly learn how this happens, but understanding that it does happen can help you notice when type annotations aren’t needed. Later, we’ll see more examples of how the context that a value occurs in can affect its type.

对象类型

¥Object Types

除了基础类型之外,你会遇到的最常见的类型是对象类型。这指的是任何带有属性的 JavaScript 值,几乎是所有属性!要定义对象类型,我们只需列出其属性及其类型。

¥Apart from primitives, the most common sort of type you’ll encounter is an object type. This refers to any JavaScript value with properties, which is almost all of them! To define an object type, we simply list its properties and their types.

例如,这是一个接受点状对象的函数:

¥For example, here’s a function that takes a point-like object:

ts
// 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 });
Try

在这里,我们用具有两个属性的类型注释了参数 - xy - 均为 number 型。你可以使用 ,; 来分隔属性,最后一个分隔符是可选的。

¥Here, we annotated the parameter with a type with two properties - x and y - which are both of type number. You can use , or ; to separate the properties, and the last separator is optional either way.

每个属性的类型部分也是可选的。如果不指定类型,则假定为 any

¥The type part of each property is also optional. If you don’t specify a type, it will be assumed to be any.

可选属性

¥Optional Properties

对象类型还可以指定它们的部分或全部属性是可选的。为此,请在属性名称后添加 ?

¥Object types can also specify that some or all of their properties are optional. To do this, add a ? after the property name:

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

在 JavaScript 中,如果你访问一个不存在的属性,你将获得值 undefined 而不是运行时错误。因此,当你从可选属性中读取数据时,你必须在使用它之前检查 undefined

¥In JavaScript, if you access a property that doesn’t exist, you’ll get the value undefined rather than a runtime error. Because of this, when you read from an optional property, you’ll have to check for undefined before using it.

ts
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
'obj.last' is possibly 'undefined'.18048'obj.last' 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());
}
Try

联合类型

¥Union Types

TypeScript 的类型系统允许你使用各种运算符从现有类型中构建新类型。现在我们知道如何编写几种类型,是时候开始以有趣的方式组合它们了。

¥TypeScript’s type system allows you to build new types out of existing ones using a large variety of operators. Now that we know how to write a few types, it’s time to start combining them in interesting ways.

定义联合类型

¥Defining a Union Type

你可能会看到的第一种组合类型的方法是联合类型。联合类型是由两种或多种其他类型组成的类型,表示可能是这些类型中的任何一种的值。我们将这些类型中的每一种都称为联合的成员。

¥The first way to combine types you might see is a union type. A union type is a type formed from two or more other types, representing values that may be any one of those types. We refer to each of these types as the union’s members.

让我们编写一个可以对字符串或数字进行操作的函数:

¥Let’s write a function that can operate on strings or numbers:

ts
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'.2345Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
Try

使用联合类型

¥Working with Union Types

提供与联合类型匹配的值很容易 - 只需提供与任何联合成员匹配的类型即可。如果你有一个联合类型的值,你如何处理它?

¥It’s easy to provide a value matching a union type - simply provide a type matching any of the union’s members. If you have a value of a union type, how do you work with it?

TypeScript 只有在对联合的每个成员都有效的情况下才允许操作。例如,如果你有联合 string | number,则不能使用仅在 string 上可用的方法:

¥TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union string | number, you can’t use methods that are only available on string:

ts
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'.2339Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.
}
Try

解决方案是用代码缩小联合,就像在没有类型注释的 JavaScript 中一样。当 TypeScript 可以根据代码的结构为某个值推断出更具体的类型时,就会发生缩小。

¥The solution is to narrow the union with code, the same as you would in JavaScript without type annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

例如,TypeScript 知道只有 string 值才会有 typeof"string"

¥For example, TypeScript knows that only a string value will have a typeof value "string":

ts
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);
}
}
Try

另一个例子是使用像 Array.isArray 这样的函数:

¥Another example is to use a function like Array.isArray:

ts
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);
}
}
Try

请注意,在 else 分支中,我们不需要做任何特殊的事情 - 如果 x 不是 string[],那么它一定是 string

¥Notice that in the else branch, we don’t need to do anything special - if x wasn’t a string[], then it must have been a string.

有时你会有一个联合,所有成员都有共同点。例如,数组和字符串都有一个 slice 方法。如果联合中的每个成员都有一个共同的属性,则可以使用该属性而不用缩小类型:

¥Sometimes you’ll have a union where all the members have something in common. For example, both arrays and strings have a slice method. If every member in a union has a property in common, you can use that property without narrowing:

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

类型的联合似乎具有这些类型的属性的交叉,这可能会令人困惑。这不是意外 - union 这个名字来自于类型理论。联合 number | string 是通过取每种类型的值的联合组成的。请注意,给定两个具有关于每个集合的相应事实的集合,只有这些事实的交叉适用于集合本身的并集。例如,如果我们有一个房间里有戴帽子的高个子,而另一个房间里有戴帽子的说西班牙语的人,在组合这些房间后,我们对每个人的唯一了解就是他们必须戴帽子。

¥It might be confusing that a union of types appears to have the intersection of those types’ properties. This is not an accident - the name union comes from type theory. The union number | string is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.

类型别名

¥Type Aliases

我们一直通过直接在类型注释中编写对象类型和联合类型来使用它们。这很方便,但通常希望多次使用同一个类型并用一个名称引用它。

¥We’ve been using object types and union types by writing them directly in type annotations. This is convenient, but it’s common to want to use the same type more than once and refer to it by a single name.

类型别名正是这样的 - 任何类型的名称。类型别名的语法是:

¥A type alias is exactly that - a name for any type. The syntax for a type alias is:

ts
type Point = {
x: number;
y: number;
};
 
// Exactly the same as the earlier 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 });
Try

实际上,你可以使用类型别名来为任何类型命名,而不仅仅是对象类型。例如,类型别名可以命名联合类型:

¥You can actually use a type alias to give a name to any type at all, not just an object type. For example, a type alias can name a union type:

ts
type ID = number | string;
Try

注意,别名只是别名 - 你不能使用类型别名来创建相同类型的不同/独特的 “版本”。当你使用别名时,就好像你已经编写了别名类型。换句话说,这段代码可能看起来非法,但根据 TypeScript 是可以的,因为这两种类型都是同一类型的别名:

¥Note that aliases are only aliases - you cannot use type aliases to create different/distinct “versions” of the same type. When you use the alias, it’s exactly as if you had written the aliased type. In other words, this code might look illegal, but is OK according to TypeScript because both types are aliases for the same type:

ts
type UserInputSanitizedString = string;
 
function sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str);
}
 
// Create a sanitized input
let userInput = sanitizeInput(getInput());
 
// Can still be re-assigned with a string though
userInput = "new input";
Try

接口

¥Interfaces

接口声明是命名对象类型的另一种方式:

¥An interface declaration is another way to name an object type:

ts
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 });
Try

就像我们在上面使用类型别名时一样,该示例就像我们使用匿名对象类型一样工作。TypeScript 只关心我们传递给 printCoord 的值的结构 - 它只关心它是否具有预期的属性。只关心类型的结构和功能是我们称 TypeScript 为结构类型类型系统的原因。

¥Just like when we used a type alias above, the example works just as if we had used an anonymous object type. TypeScript is only concerned with the structure of the value we passed to printCoord - it only cares that it has the expected properties. Being concerned only with the structure and capabilities of types is why we call TypeScript a structurally typed type system.

类型别名和接口的区别

¥Differences Between Type Aliases and Interfaces

类型别名和接口非常相似,在很多情况下你可以在它们之间自由选择。interface 的几乎所有功能都在 type 中可用,主要区别在于无法重新打开类型以添加​​新属性,而接口始终可扩展。

¥Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

Interface Type

扩展接口

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

你将在后面的章节中了解有关这些概念的更多信息,因此如果你不能立即理解所有这些概念,请不要担心。

¥You’ll learn more about these concepts in later chapters, so don’t worry if you don’t understand all of these right away.

在大多数情况下,你可以根据个人喜好进行选择,TypeScript 会告诉你是否需要其他类型的声明。如果你想要启发式方法,请使用 interface,直到你需要使用 type 中的功能。

¥For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type.

类型断言

¥Type Assertions

有时你会得到关于 TypeScript 无法知道的值类型的信息。

¥Sometimes you will have information about the type of a value that TypeScript can’t know about.

例如,如果你使用的是 document.getElementById,TypeScript 只知道这将返回某种 HTMLElement,但你可能知道你的页面将始终具有具有给定 ID 的 HTMLCanvasElement

¥For example, if you’re using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.

在这种情况下,你可以使用类型断言来指定更具体的类型:

¥In this situation, you can use a type assertion to specify a more specific type:

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

与类型注释一样,类型断言被编译器删除,不会影响代码的运行时行为。

¥Like a type annotation, type assertions are removed by the compiler and won’t affect the runtime behavior of your code.

你还可以使用尖括号语法(除非代码在 .tsx 文件中),它是等效的:

¥You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:

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

提醒:因为类型断言在编译时被删除,所以没有与类型断言关联的运行时检查。如果类型断言错误,则不会产生异常或 null

¥Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won’t be an exception or null generated if the type assertion is wrong.

TypeScript 只允许类型断言转换为更具体或更不具体的类型版本。此规则可防止 “impossible” 强制,例如:

¥TypeScript only allows type assertions which convert to a more specific or less specific version of a type. This rule prevents “impossible” coercions like:

ts
const x = "hello" as number;
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.2352Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Try

有时,此规则可能过于保守,并且不允许可能有效的更复杂的强制转换。如果发生这种情况,你可以使用两个断言,首先是 any(或 unknown,我们稍后会介绍),然后是所需的类型:

¥Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid. If this happens, you can use two assertions, first to any (or unknown, which we’ll introduce later), then to the desired type:

ts
const a = expr as any as T;
Try

字面类型

¥Literal Types

除了通用类型 stringnumber 之外,我们还可以在类型位置引用特定的字符串和数字。

¥In addition to the general types string and number, we can refer to specific strings and numbers in type positions.

考虑这一点的一种方法是考虑 JavaScript 如何使用不同的方法来声明变量。varlet 都允许更改变量中保存的内容,而 const 不允许。这反映在 TypeScript 如何为字面创建类型。

¥One way to think about this is to consider how JavaScript comes with different ways to declare a variable. Both var and let allow for changing what is held inside the variable, and const does not. This is reflected in how TypeScript creates types for literals.

ts
let changingString = "Hello World";
changingString = "Olá Mundo";
// Because `changingString` can represent any possible string, that
// is how TypeScript describes it in the type system
changingString;
let changingString: string
 
const constantString = "Hello World";
// Because `constantString` can only represent 1 possible string, it
// has a literal type representation
constantString;
const constantString: "Hello World"
Try

就其本身而言,字面类型并不是很有价值:

¥By themselves, literal types aren’t very valuable:

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

变量只能有一个值并没有多大用处!

¥It’s not much use to have a variable that can only have one value!

但是通过将字面量组合成联合,你可以表达更有用的概念 - 例如,仅接受一组特定已知值的函数:

¥But by combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:

ts
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"'.2345Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
Try

数字字面类型的工作方式相同:

¥Numeric literal types work the same way:

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

当然,你可以将这些与非字面类型结合使用:

¥Of course, you can combine these with non-literal types:

ts
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"'.2345Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.
Try

还有一种字面类型:布尔字面量。只有两种布尔字面类型,正如你可能猜到的,它们是 truefalse 类型。类型 boolean 本身实际上只是联合 true | false 的别名。

¥There’s one more kind of literal type: boolean literals. There are only two boolean literal types, and as you might guess, they are the types true and false. The type boolean itself is actually just an alias for the union true | false.

字面推断

¥Literal Inference

当你使用对象初始化变量时,TypeScript 假定该对象的属性可能会在以后更改值。例如,如果你编写如下代码:

¥When you initialize a variable with an object, TypeScript assumes that the properties of that object might change values later. For example, if you wrote code like this:

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

TypeScript 不假定将 1 分配给先前具有 0 的字段是错误的。另一种说法是 obj.counter 必须具有 number 类型,而不是 0,因为类型用于确定读取和写入行为。

¥TypeScript doesn’t assume the assignment of 1 to a field which previously had 0 is an error. Another way of saying this is that obj.counter must have the type number, not 0, because types are used to determine both reading and writing behavior.

这同样适用于字符串:

¥The same applies to strings:

ts
declare function handleRequest(url: string, method: "GET" | "POST"): void;
 
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"'.2345Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
Try

在上面的例子中,req.method 被推断为 string,而不是 "GET"。因为可以在 req 的创建和 handleRequest 的调用之间评估代码,这可以将一个新的字符串(如 "GUESS" 分配给 req.method),TypeScript 认为此代码有错误。

¥In the above example req.method is inferred to be string, not "GET". Because code can be evaluated between the creation of req and the call of handleRequest which could assign a new string like "GUESS" to req.method, TypeScript considers this code to have an error.

有两种方法可以解决这个问题。

¥There are two ways to work around this.

  1. 你可以通过在任一位置添加类型断言来更改推断:

    ¥You can change the inference by adding a type assertion in either location:

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

    更改 1 意味着“我打算让 req.method 始终具有字面量类型 "GET"”,从而防止之后可能将 "GUESS" 分配给该字段。更改 2 表示“由于其他原因我知道 req.method 的值为 "GET"”。

    ¥Change 1 means “I intend for req.method to always have the literal type "GET"”, preventing the possible assignment of "GUESS" to that field after. Change 2 means “I know for other reasons that req.method has the value "GET"“.

  2. 你可以使用 as const 将整个对象转换为类型字面:

    ¥You can use as const to convert the entire object to be type literals:

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

as const 后缀的作用类似于 const,但用于类型系统,确保为所有属性分配字面类型,而不是更通用的版本,如 stringnumber

¥The as const suffix acts like const but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string or number.

nullundefined

¥null and undefined

JavaScript 有两个基础值用于表示值不存在或未初始化的值:nullundefined

¥JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.

TypeScript 有两个对应的同名类型。这些类型的行为取决于你是否启用了 strictNullChecks 选项。

¥TypeScript has two corresponding types by the same names. How these types behave depends on whether you have the strictNullChecks option on.

strictNullChecks 关闭

¥strictNullChecks off

关闭 strictNullChecks,可能是 nullundefined 的值仍然可以正常访问,并且值 nullundefined 可以分配给任何类型的属性。这类似于没有空检查的语言(例如 C#、Java)的行为方式。缺乏对这些值的检查往往是错误的主要来源;如果在他们的代码库中这样做是可行的,我们总是建议人们打开 strictNullChecks

¥With strictNullChecks off, values that might be null or undefined can still be accessed normally, and the values null and undefined can be assigned to a property of any type. This is similar to how languages without null checks (e.g. C#, Java) behave. The lack of checking for these values tends to be a major source of bugs; we always recommend people turn strictNullChecks on if it’s practical to do so in their codebase.

strictNullChecks 开启

¥strictNullChecks on

启用 strictNullChecks 时,当值为 nullundefined 时,你需要在对该值使用方法或属性之前测试这些值。就像在使用可选属性之前检查 undefined 一样,我们可以使用缩小来检查可能是 null 的值:

¥With strictNullChecks on, when a value is null or undefined, you will need to test for those values before using methods or properties on that value. Just like checking for undefined before using an optional property, we can use narrowing to check for values that might be null:

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

非空断言运算符(后缀 !

¥Non-null Assertion Operator (Postfix !)

TypeScript 还具有一种特殊的语法,可以在不进行任何显式检查的情况下从类型中删除 nullundefined。在任何表达式之后写 ! 实际上是一个类型断言,该值不是 nullundefined

¥TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing ! after any expression is effectively a type assertion that the value isn’t null or undefined:

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

就像其他类型断言一样,这不会改变代码的运行时行为,所以当你知道值不能是 nullundefined 时,只使用 ! 很重要。

¥Just like other type assertions, this doesn’t change the runtime behavior of your code, so it’s important to only use ! when you know that the value can’t be null or undefined.

枚举

¥Enums

枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一。与大多数 TypeScript 功能不同,这不是对 JavaScript 的类型级添加,而是添加到语言和运行时的东西。正因为如此,这是一个你应该知道存在的功能,但除非你确定,否则可能会推迟使用。你可以在 枚举参考页 中阅读有关枚举的更多信息。

¥Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition to JavaScript but something added to the language and runtime. Because of this, it’s a feature which you should know exists, but maybe hold off on using unless you are sure. You can read more about enums in the Enum reference page.

不太常见的基础类型

¥Less Common Primitives

值得一提的是类型系统中表示的 JavaScript 中的其他基础类型。虽然我们不会在这里深入。

¥It’s worth mentioning the rest of the primitives in JavaScript which are represented in the type system. Though we will not go into depth here.

bigint

从 ES2020 开始,JavaScript 中有一个基础类型用于非常大的整数,BigInt

¥From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt:

ts
// Creating a bigint via the BigInt function
const oneHundred: bigint = BigInt(100);
 
// Creating a BigInt via the literal syntax
const anotherHundred: bigint = 100n;
Try

你可以在 TypeScript 3.2 发行说明 中了解有关 BigInt 的更多信息。

¥You can learn more about BigInt in the TypeScript 3.2 release notes.

symbol

JavaScript 中有一个基础类型用于通过函数 Symbol() 创建全局唯一引用:

¥There is a primitive in JavaScript used to create a globally unique reference via the function Symbol():

ts
const firstName = Symbol("name");
const secondName = Symbol("name");
 
if (firstName === secondName) {
This comparison appears to be unintentional because the types 'typeof firstName' and 'typeof secondName' have no overlap.2367This comparison appears to be unintentional because the types 'typeof firstName' and 'typeof secondName' have no overlap.
// Can't ever happen
}
Try

你可以在 符号参考页 中了解有关它们的更多信息。

¥You can learn more about them in Symbols reference page.