在本章中,我们将介绍你在 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.
基本类型:string、number 和 boolean
🌐 The primitives: string, number, and boolean
JavaScript 有三种非常常用的原始类型:string、number 和 boolean。
每一种在 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"的字符串值number用于像42这样的数字。JavaScript 没有专门针对整数的运行时值,所以没有类似int或float的东西——一切都是numberboolean适用于两个值true和false
类型名称
String、Number和Boolean(以大写字母开头)是合法的,但它们指的是一些在代码中很少出现的特殊内置类型。_始终_使用string、number或boolean来表示类型。
数组
🌐 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]是另一回事;请参阅关于 元组 的章节。
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:
tsTryletobj : 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";constn : number =obj ;
any 类型在你不想写出很长的类型来只是为了让 TypeScript 确信某一行代码没问题时非常有用。
🌐 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
当你使用 const、var 或 let 声明一个变量时,你可以选择性地添加类型注解来显式指定变量的类型:
🌐 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:
tsTryletmyName : string = "Alice";
TypeScript 不使用像
int x = 0;那样的“类型在左侧”的声明方式 类型注解总是跟在被注解的对象之后。
不过,在大多数情况下,这并不需要。尽可能地,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:
tsTry// No type annotation needed -- 'myName' inferred as type 'string'letmyName = "Alice";
大多数情况下,你不需要明确地学习推断规则。如果你是初学者,尝试使用比你认为的更少的类型注解——你可能会惊讶地发现 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:
tsTry// Parameter type annotationfunctiongreet (name : string) {console .log ("Hello, " +name .toUpperCase () + "!!");}
当参数具有类型注释时,将检查该函数的参数:
🌐 When a parameter has a type annotation, arguments to that function will be checked:
tsTry// Would be a runtime error if executed!Argument of type 'number' is not assignable to parameter of type 'string'.2345Argument of type 'number' is not assignable to parameter of type 'string'.greet (42 );
即使你的参数没有类型注解,TypeScript 仍然会检查你传递的参数数量是否正确。
返回类型注解
🌐 Return Type Annotations
你也可以添加返回类型注解。返回类型注解出现在参数列表之后:
🌐 You can also add return type annotations. Return type annotations appear after the parameter list:
tsTryfunctiongetFavoriteNumber (): number {return 26;}
就像变量类型注解一样,你通常不需要返回类型注解,因为 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:
tsTryasync functiongetFavoriteNumber ():Promise <number> {return 26;}
匿名函数
🌐 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:
tsTryconstnames = ["Alice", "Bob", "Eve"];// Contextual typing for function - parameter s inferred to have type stringnames .forEach (function (s ) {console .log (s .toUpperCase ());});// Contextual typing also applies to arrow functionsnames .forEach ((s ) => {console .log (s .toUpperCase ());});
即使参数 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:
tsTry// The parameter's type annotation is an object typefunctionprintCoord (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 });
在这里,我们给参数标注了一个具有两个属性的类型——x 和 y——它们的类型都是 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:
tsTryfunctionprintName (obj : {first : string;last ?: string }) {// ...}// Both OKprintName ({first : "Bob" });printName ({first : "Alice",last : "Alisson" });
在 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.
tsTryfunctionprintName (obj : {first : string;last ?: string }) {// Error - might crash if 'obj.last' wasn't provided!'obj.last' is possibly 'undefined'.18048'obj.last' is possibly 'undefined'.console .log (obj .last .toUpperCase ());if (obj .last !==undefined ) {// OKconsole .log (obj .last .toUpperCase ());}// A safe alternative using modern JavaScript syntax:console .log (obj .last ?.toUpperCase ());}
联合类型
🌐 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:
tsTryfunctionprintId (id : number | string) {console .log ("Your ID is: " +id );}// OKprintId (101);// OKprintId ("202");// ErrorArgument 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'.printId ({myID : 22342 });
联合类型成员的分隔符允许出现在第一个元素之前,所以你也可以这样写:
tsTryfunctionprintTextOrNumberOrBool (textOrNumberOrBool :| string| number| boolean) {console .log (textOrNumberOrBool );}
使用联合类型
🌐 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:
tsTryfunctionprintId (id : number | string) {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'.console .log (id .()); toUpperCase }
解决方法是用代码来缩小联合类型,就像在没有类型注解的 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":
tsTryfunctionprintId (id : number | string) {if (typeofid === "string") {// In this branch, id is of type 'string'console .log (id .toUpperCase ());} else {// Here, id is of type 'number'console .log (id );}}
另一个例子是使用像 Array.isArray 这样的函数:
🌐 Another example is to use a function like Array.isArray:
tsTryfunctionwelcomePeople (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 );}}
注意在 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:
tsTry// Return type is inferred as number[] | stringfunctiongetFirstThree (x : number[] | string) {returnx .slice (0, 3);}
可能会让人困惑的是,一个类型的 联合 似乎具有那些类型属性的 交集。 这并非偶然——“联合”(union) 这个名字来源于类型理论。 联合
number | string是通过取每种类型的值的并集组成的。 注意,给定两个集合及关于每个集合的相应事实时,只有那些事实的 交集 适用于集合自身的 并集。 例如,如果我们有一个房间里是戴帽子的高个子,另一个房间是戴帽子的说西班牙语的人,在合并这些房间后,我们唯一能确定的是每个人都必须戴帽子。
类型别名
🌐 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:
tsTrytypePoint = {x : number;y : number;};// Exactly the same as the earlier examplefunctionprintCoord (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 });
实际上,你可以使用类型别名为任何类型命名,而不仅仅是对象类型。例如,类型别名可以为联合类型命名:
🌐 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:
tsTrytypeID = number | string;
请注意,别名仅仅是别名——你不能使用类型别名来创建相同类型的不同/独立“版本”。 当你使用别名时,就好像你直接写了被别名引用的类型。 换句话说,这段代码看起来可能不合法,但在 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:
tsTrytypeUserInputSanitizedString = string;functionsanitizeInput (str : string):UserInputSanitizedString {returnsanitize (str );}// Create a sanitized inputletuserInput =sanitizeInput (getInput ());// Can still be re-assigned with a string thoughuserInput = "new input";
接口
🌐 Interfaces
接口声明是命名对象类型的另一种方式:
🌐 An interface declaration is another way to name an object type:
tsTryinterfacePoint {x : number;y : number;}functionprintCoord (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 });
就像我们上面使用类型别名时一样,这个例子就好像我们使用了一个匿名对象类型一样有效。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 |
|---|---|
|
Extending an interface
|
Extending a type via intersections
|
|
Adding new fields to an existing interface
|
A type cannot be changed after being created
|
你将在后面的章节中了解有关这些概念的更多信息,因此如果你不能立即理解所有这些概念,请不要担心。
🌐 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 4.2 版本之前,类型别名名称_可能_会出现在错误信息中,有时会取代等效的匿名类型(这可能是也可能不是理想的)。接口在错误信息中总是会被命名。
- 类型别名可能无法参与声明合并,但接口可以。
- 接口只能用于声明对象的形状,而不是重命名基本类型。
- 接口名称将在错误消息中_始终_以原始形式出现,但仅当它们被按名称使用时才会出现。
- 使用
extends的接口 通常比使用交叉类型的类型别名 对编译器更高效
在大多数情况下,你可以根据个人喜好选择,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:
tsTryconstmyCanvas =document .getElementById ("main_canvas") asHTMLCanvasElement ;
与类型注释一样,类型断言被编译器删除,不会影响代码的运行时行为。
🌐 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:
tsTryconstmyCanvas = <HTMLCanvasElement >document .getElementById ("main_canvas");
提醒:由于类型断言在编译时会被移除,因此类型断言不会在运行时进行检查。 如果类型断言错误,不会抛出异常或生成
null。
TypeScript 仅允许将类型断言转换为类型的 更具体 或 更宽泛 的版本。此规则可防止“不可能”的类型强制转换,例如:
🌐 TypeScript only allows type assertions which convert to a more specific or less specific version of a type. This rule prevents “impossible” coercions like:
tsTryconstConversion 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.x = "hello" as number;
有时候这个规则可能过于保守,会不允许一些可能有效的更复杂的类型强制转换。如果发生这种情况,你可以使用两个断言,首先断言为 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:
tsTryconsta =expr as any asT ;
字面类型
🌐 Literal Types
除了通用类型 string 和 number,我们还可以在类型位置引用 特定 的字符串和数字。
🌐 In addition to the general types string and number, we can refer to specific strings and numbers in type positions.
一种思考这种情况的方法是考虑 JavaScript 提供了不同的方式来声明变量。var 和 let 都允许更改变量内部存储的内容,而 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.
tsTryletchangingString = "Hello World";changingString = "Olá Mundo";// Because `changingString` can represent any possible string, that// is how TypeScript describes it in the type systemchangingString ;constconstantString = "Hello World";// Because `constantString` can only represent 1 possible string, it// has a literal type representationconstantString ;
就其本身而言,字面类型并不是很有价值:
🌐 By themselves, literal types aren’t very valuable:
tsTryletx : "hello" = "hello";// OKx = "hello";// ...Type '"howdy"' is not assignable to type '"hello"'.2322Type '"howdy"' is not assignable to type '"hello"'.= "howdy"; x
变量只能有一个值并没有多大用处!
🌐 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:
tsTryfunctionprintText (s : string,alignment : "left" | "right" | "center") {// ...}printText ("Hello, world", "left");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"'.printText ("G'day, mate","centre" );
数字字面类型的工作方式相同:
🌐 Numeric literal types work the same way:
tsTryfunctioncompare (a : string,b : string): -1 | 0 | 1 {returna ===b ? 0 :a >b ? 1 : -1;}
当然,你可以将这些与非字面类型结合使用:
🌐 Of course, you can combine these with non-literal types:
tsTryinterfaceOptions {width : number;}functionconfigure (x :Options | "auto") {// ...}configure ({width : 100 });configure ("auto");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"'.configure ("automatic" );
还有一种字面量类型:布尔字面量。 只有两种布尔字面量类型,正如你可能猜到的,它们是类型 true 和 false。 类型 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:
tsTryconstobj = {counter : 0 };if (someCondition ) {obj .counter = 1;}
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:
tsTrydeclare functionhandleRequest (url : string,method : "GET" | "POST"): void;constreq = {url : "https://example.com",method : "GET" };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"'.handleRequest (req .url ,req .method );
在上面的例子中,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.
-
你可以通过在任一位置添加类型断言来更改推断:
tsTry// Change 1:constreq = {url : "https://example.com",method : "GET" as "GET" };// Change 2handleRequest (req .url ,req .method as "GET");变更 1 意味着“我打算让
req.method始终具有 字面类型"GET"”,防止之后将"GUESS"赋给该字段。
变更 2 意味着“出于其他原因,我知道 req.method 的值是 "GET"”。
-
你可以使用
as const将整个对象转换为类型字面量:tsTryconstreq = {url : "https://example.com",method : "GET" } asconst ;handleRequest (req .url ,req .method );
as const 后缀的作用类似于 const,但用于类型系统,确保所有属性都被赋予字面量类型,而不是更通用的类型,比如 string 或 number。
🌐 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.
null 和 undefined
🌐 null and undefined
JavaScript 有两种原始值用来表示缺失或未初始化的值:null 和 undefined。
🌐 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 关闭 时,可能是 null 或 undefined 的值仍然可以正常访问,并且 null 和 undefined 的值可以分配给任何类型的属性。 这类似于没有空检查的语言(例如 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 时,当一个值是 null 或 undefined 时,你需要在使用该值的方法或属性之前先对这些值进行测试。就像在使用可选属性之前检查 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:
tsTryfunctiondoSomething (x : string | null) {if (x === null) {// do nothing} else {console .log ("Hello, " +x .toUpperCase ());}}
非空断言操作符(后缀 !)
🌐 Non-null Assertion Operator (Postfix !)
TypeScript 也有一种特殊语法,可以在不进行任何显式检查的情况下从类型中移除 null 和 undefined。
在任何表达式后写 ! 实际上是一种类型断言,表示该值不是 null 或 undefined:
🌐 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:
tsTryfunctionliveDangerously (x ?: number | null) {// No errorconsole .log (x !.toFixed ());}
就像其他类型断言一样,这并不会改变代码的运行时行为,因此只有在你确定该值 不可能 是 null 或 undefined 时,才应使用 !。
🌐 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:
tsTry// Creating a bigint via the BigInt functionconstoneHundred : bigint =BigInt (100);// Creating a BigInt via the literal syntaxconstanotherHundred : bigint = 100n;
你可以在 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():
tsTryconstfirstName =Symbol ("name");constsecondName =Symbol ("name");if (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.firstName ===secondName ) {// Can't ever happen}
你可以在符号参考页面了解更多关于它们的信息。
🌐 You can learn more about them in Symbols reference page.