在 JavaScript 中,我们分组和传递数据的基本方式是通过对象。在 TypeScript 中,我们通过对象类型来表示它们。
¥In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types.
正如我们所见,它们可以是匿名的:
¥As we’ve seen, they can be anonymous:
tsTry
functiongreet (person : {name : string;age : number }) {return "Hello " +person .name ;}
或者它们可以通过使用接口来命名:
¥or they can be named by using either an interface:
tsTry
interfacePerson {name : string;age : number;}functiongreet (person :Person ) {return "Hello " +person .name ;}
或类型别名:
¥or a type alias:
tsTry
typePerson = {name : string;age : number;};functiongreet (person :Person ) {return "Hello " +person .name ;}
在上述所有三个示例中,我们编写的函数接受包含属性 name
(必须是 string
)和 age
(必须是 number
)的对象。
¥In all three examples above, we’ve written functions that take objects that contain the property name
(which must be a string
) and age
(which must be a number
).
快速参考
¥Quick Reference
如果你想快速浏览重要的日常语法,我们为 type
和 interface
提供备忘单。
¥We have cheat-sheets available for both type
and interface
, if you want a quick look at the important every-day syntax at a glance.
属性修饰符
¥Property Modifiers
对象类型中的每个属性都可以指定一些内容:类型,属性是否可选,属性是否可以写入。
¥Each property in an object type can specify a couple of things: the type, whether the property is optional, and whether the property can be written to.
可选属性
¥Optional Properties
很多时候,我们会发现自己在处理可能具有属性集的对象。在这些情况下,我们可以通过在其名称末尾添加问号 (?
) 来将这些属性标记为可选。
¥Much of the time, we’ll find ourselves dealing with objects that might have a property set.
In those cases, we can mark those properties as optional by adding a question mark (?
) to the end of their names.
tsTry
interfacePaintOptions {shape :Shape ;xPos ?: number;yPos ?: number;}functionpaintShape (opts :PaintOptions ) {// ...}constshape =getShape ();paintShape ({shape });paintShape ({shape ,xPos : 100 });paintShape ({shape ,yPos : 100 });paintShape ({shape ,xPos : 100,yPos : 100 });
在这个例子中,xPos
和 yPos
都被认为是可选的。我们可以选择提供其中任何一个,因此上面对 paintShape
的每个调用都是有效的。所有的可选性真正说明的是,如果设置了属性,它最好有一个特定的类型。
¥In this example, both xPos
and yPos
are considered optional.
We can choose to provide either of them, so every call above to paintShape
is valid.
All optionality really says is that if the property is set, it better have a specific type.
我们还可以从这些属性中读取 - 但是当我们在 strictNullChecks
下进行操作时,TypeScript 会告诉我们它们可能是 undefined
。
¥We can also read from those properties - but when we do under strictNullChecks
, TypeScript will tell us they’re potentially undefined
.
tsTry
functionpaintShape (opts :PaintOptions ) {letxPos =opts .xPos ;letyPos =opts .yPos ;// ...}
在 JavaScript 中,即使属性从未被设置过,我们仍然可以访问它 - 它只会给我们值 undefined
。我们可以通过检查来专门处理 undefined
。
¥In JavaScript, even if the property has never been set, we can still access it - it’s just going to give us the value undefined
.
We can just handle undefined
specially by checking for it.
tsTry
functionpaintShape (opts :PaintOptions ) {letxPos =opts .xPos ===undefined ? 0 :opts .xPos ;letyPos =opts .yPos ===undefined ? 0 :opts .yPos ;// ...}
请注意,这种为未指定值设置默认值的模式非常普遍,以至于 JavaScript 有语法来支持它。
¥Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to support it.
tsTry
functionpaintShape ({shape ,xPos = 0,yPos = 0 }:PaintOptions ) {console .log ("x coordinate at",xPos );console .log ("y coordinate at",yPos );// ...}
这里我们使用 解构模式 作为 paintShape
的参数,并为 xPos
和 yPos
提供了 默认值。现在 xPos
和 yPos
都肯定存在于 paintShape
的主体中,但对于 paintShape
的任何调用者都是可选的。
¥Here we used a destructuring pattern for paintShape
’s parameter, and provided default values for xPos
and yPos
.
Now xPos
and yPos
are both definitely present within the body of paintShape
, but optional for any callers to paintShape
.
请注意,目前没有办法在解构模式中放置类型注释。这是因为下面的语法在 JavaScript 中已经有了不同的含义。
¥Note that there is currently no way to place type annotations within destructuring patterns. This is because the following syntax already means something different in JavaScript.
tsTry
functiondraw ({shape :Shape ,xPos :number = 100 /*...*/ }) {Cannot find name 'shape'. Did you mean 'Shape'?2552Cannot find name 'shape'. Did you mean 'Shape'?render (); shape Cannot find name 'xPos'.2304Cannot find name 'xPos'.render (); xPos }在对象解构模式中,
shape: Shape
表示“获取属性shape
并将其在本地重新定义为名为Shape
的变量”。 同样,xPos: number
创建一个名为number
的变量,其值基于参数的xPos
。¥In an object destructuring pattern,
shape: Shape
means “grab the propertyshape
and redefine it locally as a variable namedShape
.” LikewisexPos: number
creates a variable namednumber
whose value is based on the parameter’sxPos
.
readonly
属性
¥readonly
Properties
对于 TypeScript,属性也可以标记为 readonly
。虽然它不会在运行时改变任何行为,但在类型检查期间无法写入标记为 readonly
的属性。
¥Properties can also be marked as readonly
for TypeScript.
While it won’t change any behavior at runtime, a property marked as readonly
can’t be written to during type-checking.
tsTry
interfaceSomeType {readonlyprop : string;}functiondoSomething (obj :SomeType ) {// We can read from 'obj.prop'.console .log (`prop has the value '${obj .prop }'.`);// But we can't re-assign it.Cannot assign to 'prop' because it is a read-only property.2540Cannot assign to 'prop' because it is a read-only property.obj .= "hello"; prop }
使用 readonly
修饰符并不一定意味着值是完全不可变的 - 或者换句话说,其内部内容无法更改。这只是意味着属性本身不能被重写。
¥Using the readonly
modifier doesn’t necessarily imply that a value is totally immutable - or in other words, that its internal contents can’t be changed.
It just means the property itself can’t be re-written to.
tsTry
interfaceHome {readonlyresident : {name : string;age : number };}functionvisitForBirthday (home :Home ) {// We can read and update properties from 'home.resident'.console .log (`Happy birthday ${home .resident .name }!`);home .resident .age ++;}functionevict (home :Home ) {// But we can't write to the 'resident' property itself on a 'Home'.Cannot assign to 'resident' because it is a read-only property.2540Cannot assign to 'resident' because it is a read-only property.home .= { resident name : "Victor the Evictor",age : 42,};}
管理对 readonly
含义的期望很重要。在 TypeScript 的开发期间触发关于如何使用对象的意图很有用。TypeScript 在检查两种类型是否兼容时不会考虑这两种类型的属性是否为 readonly
,因此 readonly
属性也可以通过别名来更改。
¥It’s important to manage expectations of what readonly
implies.
It’s useful to signal intent during development time for TypeScript on how an object should be used.
TypeScript doesn’t factor in whether properties on two types are readonly
when checking whether those types are compatible, so readonly
properties can also change via aliasing.
tsTry
interfacePerson {name : string;age : number;}interfaceReadonlyPerson {readonlyname : string;readonlyage : number;}letwritablePerson :Person = {name : "Person McPersonface",age : 42,};// worksletreadonlyPerson :ReadonlyPerson =writablePerson ;console .log (readonlyPerson .age ); // prints '42'writablePerson .age ++;console .log (readonlyPerson .age ); // prints '43'
使用 映射修饰符,你可以删除 readonly
属性。
¥Using mapping modifiers, you can remove readonly
attributes.
索引签名
¥Index Signatures
有时你并不提前知道类型属性的所有名称,但你确实知道值的形状。
¥Sometimes you don’t know all the names of a type’s properties ahead of time, but you do know the shape of the values.
在这些情况下,你可以使用索引签名来描述可能值的类型,例如:
¥In those cases you can use an index signature to describe the types of possible values, for example:
tsTry
interfaceStringArray {[index : number]: string;}constmyArray :StringArray =getStringArray ();constsecondItem =myArray [1];
上面,我们有一个 StringArray
接口,它有一个索引签名。这个索引签名表明当一个 StringArray
被一个 number
索引时,它将返回一个 string
。
¥Above, we have a StringArray
interface which has an index signature.
This index signature states that when a StringArray
is indexed with a number
, it will return a string
.
索引签名属性只允许使用某些类型:string
、number
、symbol
、模板字符串模式以及仅由这些组成的联合类型。
¥Only some types are allowed for index signature properties: string
, number
, symbol
, template string patterns, and union types consisting only of these.
可以支持多种类型的索引器...
可以支持多种类型的索引器。请注意,当同时使用 `number` 和 `string` 索引器时,从数字索引器返回的类型必须是从字符串索引器返回的类型的子类型。这是因为当使用 number
进行索引时,JavaScript 在索引到对象之前实际上会将其转换为 string
。这意味着使用 XSPACE100
( number
)建立索引与使用 "100"
( string
)建立索引是一样的,因此两者需要保持一致。
tsTry
interfaceAnimal {name : string;}interfaceDog extendsAnimal {breed : string;}// Error: indexing with a numeric string might get you a completely separate type of Animal!interfaceNotOkay {['number' index type 'Animal' is not assignable to 'string' index type 'Dog'.2413'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.x : number]:Animal ;[x : string]:Dog ;}
虽然字符串索引签名是描述 “dictionary” 模式的强大方式,但它们还强制所有属性与其返回类型匹配。这是因为字符串索引声明 obj.property
也可用作 obj["property"]
。在下面的例子中,name
的类型与字符串索引的类型不匹配,类型检查器给出错误:
¥While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type.
This is because a string index declares that obj.property
is also available as obj["property"]
.
In the following example, name
’s type does not match the string index’s type, and the type checker gives an error:
tsTry
interfaceNumberDictionary {[index : string]: number;length : number; // okProperty 'name' of type 'string' is not assignable to 'string' index type 'number'.2411Property 'name' of type 'string' is not assignable to 'string' index type 'number'.: string; name }
但是,如果索引签名是属性类型的联合,则可以接受不同类型的属性:
¥However, properties of different types are acceptable if the index signature is a union of the property types:
tsTry
interfaceNumberOrStringDictionary {[index : string]: number | string;length : number; // ok, length is a numbername : string; // ok, name is a string}
最后,你可以使索引签名 readonly
以防止分配给它们的索引:
¥Finally, you can make index signatures readonly
in order to prevent assignment to their indices:
tsTry
interfaceReadonlyStringArray {readonly [index : number]: string;}letmyArray :ReadonlyStringArray =getReadOnlyStringArray ();Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.myArray [2] = "Mallory";
你不能设置 myArray[2]
,因为索引签名是 readonly
。
¥You can’t set myArray[2]
because the index signature is readonly
.
溢出属性检查
¥Excess Property Checks
在何处以及如何为对象分配类型可以在类型系统中产生差异。这方面的一个关键示例是溢出属性检查,它会在创建对象时更彻底地验证对象并在创建期间将其分配给对象类型。
¥Where and how an object is assigned a type can make a difference in the type system. One of the key examples of this is in excess property checking, which validates the object more thoroughly when it is created and assigned to an object type during creation.
tsTry
interfaceSquareConfig {color ?: string;width ?: number;}functioncreateSquare (config :SquareConfig ): {color : string;area : number } {return {color :config .color || "red",area :config .width ?config .width *config .width : 20,};}letObject literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2561Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?mySquare =createSquare ({: "red", colour width : 100 });
请注意,createSquare
的给定参数拼写为 colour
而不是 color
。在普通的 JavaScript 中,这种事情会悄无声息地失败。
¥Notice the given argument to createSquare
is spelled colour
instead of color
.
In plain JavaScript, this sort of thing fails silently.
你可能会争辩说这个程序的类型是正确的,因为 width
属性是兼容的,没有 color
属性存在,额外的 colour
属性是微不足道的。
¥You could argue that this program is correctly typed, since the width
properties are compatible, there’s no color
property present, and the extra colour
property is insignificant.
但是,TypeScript 认为这段代码中可能存在错误。对象字面在将它们分配给其他变量或将它们作为参数传递时会得到特殊处理并进行额外的属性检查。如果一个对象字面量有任何 “目标类型” 没有的属性,你会得到一个错误:
¥However, TypeScript takes the stance that there’s probably a bug in this code. Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error:
tsTry
letObject literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2561Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?mySquare =createSquare ({: "red", colour width : 100 });
绕过这些检查实际上非常简单。最简单的方法是只使用类型断言:
¥Getting around these checks is actually really simple. The easiest method is to just use a type assertion:
tsTry
letmySquare =createSquare ({width : 100,opacity : 0.5 } asSquareConfig );
但是,如果你确定该对象可以具有一些以某种特殊方式使用的额外属性,则更好的方法可能是添加字符串索引签名。如果 SquareConfig
可以具有上述类型的 color
和 width
属性,但也可以具有任意数量的其他属性,那么我们可以这样定义它:
¥However, a better approach might be to add a string index signature if you’re sure that the object can have some extra properties that are used in some special way.
If SquareConfig
can have color
and width
properties with the above types, but could also have any number of other properties, then we could define it like so:
tsTry
interfaceSquareConfig {color ?: string;width ?: number;[propName : string]: unknown;}
这里我们说 SquareConfig
可以有任意数量的属性,只要它们不是 color
或 width
,它们的类型并不重要。
¥Here we’re saying that SquareConfig
can have any number of properties, and as long as they aren’t color
or width
, their types don’t matter.
绕过这些检查的最后一种方法(可能有点令人惊讶)是将对象分配给另一个变量:由于分配 squareOptions
不会进行溢出属性检查,因此编译器不会给你错误:
¥One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable:
Since assigning squareOptions
won’t undergo excess property checks, the compiler won’t give you an error:
tsTry
letsquareOptions = {colour : "red",width : 100 };letmySquare =createSquare (squareOptions );
只要你在 squareOptions
和 SquareConfig
之间具有共同属性,上述变通方法就会起作用。在此示例中,它是属性 width
。但是,如果变量没有任何公共对象属性,它将失败。例如:
¥The above workaround will work as long as you have a common property between squareOptions
and SquareConfig
.
In this example, it was the property width
. It will however, fail if the variable does not have any common object property. For example:
tsTry
letsquareOptions = {colour : "red" };letType '{ colour: string; }' has no properties in common with type 'SquareConfig'.2559Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.mySquare =createSquare (); squareOptions
请记住,对于像上面这样的简单代码,你可能不应该尝试对这些检查进行 “到处走走”。对于具有方法和保持状态的更复杂的对象字面量,你可能需要牢记这些技术,但大多数溢出属性错误实际上是错误。
¥Keep in mind that for simple code like above, you probably shouldn’t be trying to “get around” these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs.
这意味着如果你遇到诸如选项包之类的溢出属性检查问题,你可能需要修改一些类型声明。在这种情况下,如果可以将具有 color
或 colour
属性的对象传递给 createSquare
,则应该修改 SquareConfig
的定义以反映这一点。
¥That means if you’re running into excess property checking problems for something like option bags, you might need to revise some of your type declarations.
In this instance, if it’s okay to pass an object with both a color
or colour
property to createSquare
, you should fix up the definition of SquareConfig
to reflect that.
扩展类型
¥Extending Types
拥有可能是其他类型的更具体版本的类型是很常见的。例如,我们可能有一个 BasicAddress
类型,它描述了在美国发送信件和包所需的字段。
¥It’s pretty common to have types that might be more specific versions of other types.
For example, we might have a BasicAddress
type that describes the fields necessary for sending letters and packages in the U.S.
tsTry
interfaceBasicAddress {name ?: string;street : string;city : string;country : string;postalCode : string;}
在某些情况下这就足够了,但如果某个地址的楼房有多个单元,则地址通常有一个与之关联的单元号。然后我们可以描述一个 AddressWithUnit
。
¥In some situations that’s enough, but addresses often have a unit number associated with them if the building at an address has multiple units.
We can then describe an AddressWithUnit
.
tsTry
interfaceAddressWithUnit {name ?: string;unit : string;street : string;city : string;country : string;postalCode : string;}
这可以完成工作,但这里的缺点是当我们的更改纯粹是添加时,我们必须重复 BasicAddress
中的所有其他字段。相反,我们可以扩展原来的 BasicAddress
类型,只添加 AddressWithUnit
独有的新字段。
¥This does the job, but the downside here is that we had to repeat all the other fields from BasicAddress
when our changes were purely additive.
Instead, we can extend the original BasicAddress
type and just add the new fields that are unique to AddressWithUnit
.
tsTry
interfaceBasicAddress {name ?: string;street : string;city : string;country : string;postalCode : string;}interfaceAddressWithUnit extendsBasicAddress {unit : string;}
interface
上的 extends
关键字允许我们有效地从其他命名类型复制成员,并添加我们想要的任何新成员。这对于减少我们必须编写的类型声明样板的数量以及表明同一属性的几个不同声明可能相关的意图很有用。例如,AddressWithUnit
不需要重复 street
属性,因为 street
源自 BasicAddress
,所以读者会知道这两种类型在某种程度上是相关的。
¥The extends
keyword on an interface
allows us to effectively copy members from other named types, and add whatever new members we want.
This can be useful for cutting down the amount of type declaration boilerplate we have to write, and for signaling intent that several different declarations of the same property might be related.
For example, AddressWithUnit
didn’t need to repeat the street
property, and because street
originates from BasicAddress
, a reader will know that those two types are related in some way.
interface
还可以从多种类型扩展。
¥interface
s can also extend from multiple types.
tsTry
interfaceColorful {color : string;}interfaceCircle {radius : number;}interfaceColorfulCircle extendsColorful ,Circle {}constcc :ColorfulCircle = {color : "red",radius : 42,};
交叉类型
¥Intersection Types
interface
允许我们通过扩展其他类型来构建新类型。TypeScript 提供了另一种称为交叉类型的构造,主要用于组合现有的对象类型。
¥interface
s allowed us to build up new types from other types by extending them.
TypeScript provides another construct called intersection types that is mainly used to combine existing object types.
交叉类型是使用 &
运算符定义的。
¥An intersection type is defined using the &
operator.
tsTry
interfaceColorful {color : string;}interfaceCircle {radius : number;}typeColorfulCircle =Colorful &Circle ;
在这里,我们将 Colorful
和 Circle
相交以生成一个包含 Colorful
和 Circle
的所有成员的新类型。
¥Here, we’ve intersected Colorful
and Circle
to produce a new type that has all the members of Colorful
and Circle
.
tsTry
functiondraw (circle :Colorful &Circle ) {console .log (`Color was ${circle .color }`);console .log (`Radius was ${circle .radius }`);}// okaydraw ({color : "blue",radius : 42 });// oopsObject literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?2561Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?draw ({color : "red",: 42 }); raidus
接口扩展与交叉
¥Interface Extension vs. Intersection
我们只是研究了两种组合相似但实际上略有不同的类型的方法。使用接口,我们可以使用 extends
子句从其他类型扩展,我们可以对交叉做类似的事情,并用类型别名命名结果。两者之间的主要区别在于冲突的处理方式,这种区别通常是你在接口和交叉类型的类型别名之间选择一个而不是另一个的主要原因之一。
¥We just looked at two ways to combine types which are similar, but are actually subtly different.
With interfaces, we could use an extends
clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias.
The principal difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you’d pick one over the other between an interface and a type alias of an intersection type.
如果接口使用相同的名称定义,则 TypeScript 将尝试在属性兼容的情况下合并它们。如果属性不兼容(即,它们具有相同的属性名称但类型不同),TypeScript 将引发错误。
¥If interfaces are defined with the same name, TypeScript will attempt to merge them if the properties are compatible. If the properties are not compatible (i.e., they have the same property name but different types), TypeScript will raise an error.
在交叉类型的情况下,具有不同类型的属性将自动合并。当稍后使用该类型时,TypeScript 将期望该属性同时满足两种类型,这可能会产生意外结果。
¥In the case of intersection types, properties with different types will be merged automatically. When the type is used later, TypeScript will expect the property to satisfy both types simultaneously, which may produce unexpected results.
例如,以下代码将抛出错误,因为属性不兼容:
¥For example, the following code will throw an error because the properties are incompatible:
ts
interface Person {name: string;}interface Person {name: number;}
相反,以下代码将编译,但会产生 never
类型:
¥In contrast, the following code will compile, but it results in a never
type:
tsTry
interfacePerson1 {name : string;}interfacePerson2 {name : number;}typeStaff =Person1 &Person2 declare conststaffer :Staff ;staffer .name ;
在这种情况下,Staff 需要 name 属性既是字符串又是数字,这会导致属性为 never
类型。
¥In this case, Staff would require the name property to be both a string and a number, which results in property being of type never
.
泛型对象类型
¥Generic Object Types
让我们想象一个可以包含任何值的 Box
类型 - string
、number
、Giraffe
,等等。
¥Let’s imagine a Box
type that can contain any value - string
s, number
s, Giraffe
s, whatever.
tsTry
interfaceBox {contents : any;}
现在,contents
属性的类型为 any
,虽然有效,但可能会导致事故发生。
¥Right now, the contents
property is typed as any
, which works, but can lead to accidents down the line.
我们可以改用 unknown
,但这意味着在我们已经知道 contents
的类型的情况下,我们需要进行预防性检查,或者使用容易出错的类型断言。
¥We could instead use unknown
, but that would mean that in cases where we already know the type of contents
, we’d need to do precautionary checks, or use error-prone type assertions.
tsTry
interfaceBox {contents : unknown;}letx :Box = {contents : "hello world",};// we could check 'x.contents'if (typeofx .contents === "string") {console .log (x .contents .toLowerCase ());}// or we could use a type assertionconsole .log ((x .contents as string).toLowerCase ());
一种类型安全的方法是为每种类型的 contents
搭建不同的 Box
类型。
¥One type safe approach would be to instead scaffold out different Box
types for every type of contents
.
tsTry
interfaceNumberBox {contents : number;}interfaceStringBox {contents : string;}interfaceBooleanBox {contents : boolean;}
但这意味着我们必须创建不同的函数或函数重载,才能对这些类型进行操作。
¥But that means we’ll have to create different functions, or overloads of functions, to operate on these types.
tsTry
functionsetContents (box :StringBox ,newContents : string): void;functionsetContents (box :NumberBox ,newContents : number): void;functionsetContents (box :BooleanBox ,newContents : boolean): void;functionsetContents (box : {contents : any },newContents : any) {box .contents =newContents ;}
这是很多样板。此外,我们稍后可能需要引入新的类型和重载。这令人沮丧,因为我们的盒子类型和重载实际上都是相同的。
¥That’s a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frustrating, since our box types and overloads are all effectively the same.
相反,我们可以创建一个声明类型参数的泛型 Box
类型。
¥Instead, we can make a generic Box
type which declares a type parameter.
tsTry
interfaceBox <Type > {contents :Type ;}
你可能会将其理解为“Type
的 Box
是其 contents
具有类型 Type
的东西”。稍后,当我们引用 Box
时,我们必须给出一个类型参数来代替 Type
。
¥You might read this as “A Box
of Type
is something whose contents
have type Type
”.
Later on, when we refer to Box
, we have to give a type argument in place of Type
.
tsTry
letbox :Box <string>;
将 Box
视为真值类型的模板,其中 Type
是一个占位符,将被其他类型替换。当 TypeScript 看到 Box<string>
时,它会将 Box<Type>
中的每个 Type
实例替换为 string
,并最终使用 { contents: string }
之类的东西。换言之,Box<string>
和我们之前的 StringBox
工作方式相同。
¥Think of Box
as a template for a real type, where Type
is a placeholder that will get replaced with some other type.
When TypeScript sees Box<string>
, it will replace every instance of Type
in Box<Type>
with string
, and end up working with something like { contents: string }
.
In other words, Box<string>
and our earlier StringBox
work identically.
tsTry
interfaceBox <Type > {contents :Type ;}interfaceStringBox {contents : string;}letboxA :Box <string> = {contents : "hello" };boxA .contents ;letboxB :StringBox = {contents : "world" };boxB .contents ;
Box
是可重用的,因为 Type
可以用任何东西代替。这意味着当我们需要一个新类型的盒子时,我们根本不需要声明一个新的 Box
类型(尽管如果我们愿意,我们当然可以)。
¥Box
is reusable in that Type
can be substituted with anything. That means that when we need a box for a new type, we don’t need to declare a new Box
type at all (though we certainly could if we wanted to).
tsTry
interfaceBox <Type > {contents :Type ;}interfaceApple {// ....}// Same as '{ contents: Apple }'.typeAppleBox =Box <Apple >;
这也意味着我们可以通过使用 泛型函数 来完全避免重载。
¥This also means that we can avoid overloads entirely by instead using generic functions.
tsTry
functionsetContents <Type >(box :Box <Type >,newContents :Type ) {box .contents =newContents ;}
值得注意的是,类型别名也可以是泛型的。我们可以定义新的 Box<Type>
接口,它是:
¥It is worth noting that type aliases can also be generic. We could have defined our new Box<Type>
interface, which was:
tsTry
interfaceBox <Type > {contents :Type ;}
通过使用类型别名来代替:
¥by using a type alias instead:
tsTry
typeBox <Type > = {contents :Type ;};
由于类型别名与接口不同,它可以描述的不仅仅是对象类型,我们也可以使用它们来编写其他类型的泛型辅助程序类型。
¥Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.
tsTry
typeOrNull <Type > =Type | null;typeOneOrMany <Type > =Type |Type [];typeOneOrManyOrNull <Type > =OrNull <OneOrMany <Type >>;typeOneOrManyOrNullStrings =OneOrManyOrNull <string>;
稍后我们将回过头来输入别名。
¥We’ll circle back to type aliases in just a little bit.
Array
类型
¥The Array
Type
泛型对象类型通常是某种容器类型,它们独立于它们所包含的元素类型工作。数据结构以这种方式工作是理想的,这样它们就可以在不同的数据类型中重用。
¥Generic object types are often some sort of container type that work independently of the type of elements they contain. It’s ideal for data structures to work this way so that they’re re-usable across different data types.
事实证明,在本手册中,我们一直在使用一种类型:Array
型。每当我们写出像 number[]
或 string[]
这样的类型时,这实际上只是 Array<number>
和 Array<string>
的简写。
¥It turns out we’ve been working with a type just like that throughout this handbook: the Array
type.
Whenever we write out types like number[]
or string[]
, that’s really just a shorthand for Array<number>
and Array<string>
.
tsTry
functiondoSomething (value :Array <string>) {// ...}letmyArray : string[] = ["hello", "world"];// either of these work!doSomething (myArray );doSomething (newArray ("hello", "world"));
很像上面的 Box
类型,Array
本身是一个泛型类型。
¥Much like the Box
type above, Array
itself is a generic type.
tsTry
interfaceGlobal type 'Array' must have 1 type parameter(s).< Array Type > {
All declarations of 'Array' must have identical type parameters.2317
2428Global type 'Array' must have 1 type parameter(s).
All declarations of 'Array' must have identical type parameters./*** Gets or sets the length of the array.*/length : number;/*** Removes the last element from an array and returns it.*/pop ():Type | undefined;/*** Appends new elements to an array, and returns the new length of the array.*/A rest parameter must be of an array type.2370A rest parameter must be of an array type.push (...items :Type []): number;// ...}
现代 JavaScript 还提供了其他泛型的数据结构,如 Map<K, V>
、Set<T>
和 Promise<T>
。所有这一切真正意味着由于 Map
、Set
和 Promise
的行为方式,它们可以与任何类型的集合一起使用。
¥Modern JavaScript also provides other data structures which are generic, like Map<K, V>
, Set<T>
, and Promise<T>
.
All this really means is that because of how Map
, Set
, and Promise
behave, they can work with any sets of types.
ReadonlyArray
类型
¥The ReadonlyArray
Type
ReadonlyArray
是一种特殊类型,用于描述不应更改的数组。
¥The ReadonlyArray
is a special type that describes arrays that shouldn’t be changed.
tsTry
functiondoStuff (values :ReadonlyArray <string>) {// We can read from 'values'...constcopy =values .slice ();console .log (`The first value is ${values [0]}`);// ...but we can't mutate 'values'.Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.values .("hello!"); push }
就像属性的 readonly
修饰符一样,它主要是我们可以用于意图的工具。当我们看到一个返回 ReadonlyArray
的函数时,它告诉我们根本不打算更改内容,而当我们看到一个消耗 ReadonlyArray
的函数时,它告诉我们可以将任何数组传递到该函数中,而不必担心它会更改其内容。
¥Much like the readonly
modifier for properties, it’s mainly a tool we can use for intent.
When we see a function that returns ReadonlyArray
s, it tells us we’re not meant to change the contents at all, and when we see a function that consumes ReadonlyArray
s, it tells us that we can pass any array into that function without worrying that it will change its contents.
与 Array
不同,我们没有可以使用的 ReadonlyArray
构造函数。
¥Unlike Array
, there isn’t a ReadonlyArray
constructor that we can use.
tsTry
new'ReadonlyArray' only refers to a type, but is being used as a value here.2693'ReadonlyArray' only refers to a type, but is being used as a value here.("red", "green", "blue"); ReadonlyArray
相反,我们可以将常规 Array
分配给 ReadonlyArray
。
¥Instead, we can assign regular Array
s to ReadonlyArray
s.
tsTry
constroArray :ReadonlyArray <string> = ["red", "green", "blue"];
正如 TypeScript 为 Array<Type>
和 Type[]
提供简写语法一样,它也为 ReadonlyArray<Type>
和 readonly Type[]
提供简写语法。
¥Just as TypeScript provides a shorthand syntax for Array<Type>
with Type[]
, it also provides a shorthand syntax for ReadonlyArray<Type>
with readonly Type[]
.
tsTry
functiondoStuff (values : readonly string[]) {// We can read from 'values'...constcopy =values .slice ();console .log (`The first value is ${values [0]}`);// ...but we can't mutate 'values'.Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.values .("hello!"); push }
最后要注意的一点是,与 readonly
属性修饰符不同,可赋值性在常规 Array
和 ReadonlyArray
之间不是双向的。
¥One last thing to note is that unlike the readonly
property modifier, assignability isn’t bidirectional between regular Array
s and ReadonlyArray
s.
tsTry
letx : readonly string[] = [];lety : string[] = [];x =y ;The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.4104The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.= y x ;
元组类型
¥Tuple Types
元组类型是另一种 Array
类型,它确切地知道它包含多少个元素,以及它在特定位置包含哪些类型。
¥A tuple type is another sort of Array
type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.
tsTry
typeStringNumberPair = [string, number];
这里,StringNumberPair
是 string
和 number
的元组类型。与 ReadonlyArray
一样,它在运行时没有表示,但对 TypeScript 很重要。对于类型系统,StringNumberPair
描述了 0
索引包含 string
和 1
索引包含 number
的数组。
¥Here, StringNumberPair
is a tuple type of string
and number
.
Like ReadonlyArray
, it has no representation at runtime, but is significant to TypeScript.
To the type system, StringNumberPair
describes arrays whose 0
index contains a string
and whose 1
index contains a number
.
tsTry
functiondoSomething (pair : [string, number]) {consta =pair [0];constb =pair [1];// ...}doSomething (["hello", 42]);
如果我们试图索引超过元素的数量,我们会得到一个错误。
¥If we try to index past the number of elements, we’ll get an error.
tsTry
functiondoSomething (pair : [string, number]) {// ...constTuple type '[string, number]' of length '2' has no element at index '2'.2493Tuple type '[string, number]' of length '2' has no element at index '2'.c =pair [2 ];}
我们也可以使用 JavaScript 的数组解构来 解构元组。
¥We can also destructure tuples using JavaScript’s array destructuring.
tsTry
functiondoSomething (stringHash : [string, number]) {const [inputString ,hash ] =stringHash ;console .log (inputString );console .log (hash );}
元组类型在大量基于约定的 API 中很有用,其中每个元素的含义都是 “明确的”。这使我们在解构变量时可以灵活地命名变量。在上面的示例中,我们可以将元素
0
和1
命名为我们想要的任何名称。¥Tuple types are useful in heavily convention-based APIs, where each element’s meaning is “obvious”. This gives us flexibility in whatever we want to name our variables when we destructure them. In the above example, we were able to name elements
0
and1
to whatever we wanted.但是,由于并非每个用户都对显而易见的事物持有相同的看法,因此可能值得重新考虑使用具有描述性属性名称的对象是否更适合你的 API。
¥However, since not every user holds the same view of what’s obvious, it may be worth reconsidering whether using objects with descriptive property names may be better for your API.
除了这些长度检查之外,像这样的简单元组类型相当于声明特定索引属性的 Array
版本的类型,以及使用数字字面量类型声明 length
的类型。
¥Other than those length checks, simple tuple types like these are equivalent to types which are versions of Array
s that declare properties for specific indexes, and that declare length
with a numeric literal type.
tsTry
interfaceStringNumberPair {// specialized propertieslength : 2;0: string;1: number;// Other 'Array<string | number>' members...slice (start ?: number,end ?: number):Array <string | number>;}
你可能感兴趣的另一件事是元组可以通过写出问号(元素类型后的 ?
)来具有可选属性。可选的元组元素只能放在最后,也会影响 length
的类型。
¥Another thing you may be interested in is that tuples can have optional properties by writing out a question mark (?
after an element’s type).
Optional tuple elements can only come at the end, and also affect the type of length
.
tsTry
typeEither2dOr3d = [number, number, number?];functionsetCoordinate (coord :Either2dOr3d ) {const [x ,y ,z ] =coord ;console .log (`Provided coordinates had ${coord .length } dimensions`);}
元组也可以有剩余元素,它们必须是数组/元组类型。
¥Tuples can also have rest elements, which have to be an array/tuple type.
tsTry
typeStringNumberBooleans = [string, number, ...boolean[]];typeStringBooleansNumber = [string, ...boolean[], number];typeBooleansStringNumber = [...boolean[], string, number];
-
StringNumberBooleans
描述了一个元组,其前两个元素分别是string
和number
,但后面可以有任意数量的boolean
。¥
StringNumberBooleans
describes a tuple whose first two elements arestring
andnumber
respectively, but which may have any number ofboolean
s following. -
StringBooleansNumber
描述一个元组,其第一个元素是string
,然后是任意数量的boolean
,最后以number
结尾。¥
StringBooleansNumber
describes a tuple whose first element isstring
and then any number ofboolean
s and ending with anumber
. -
BooleansStringNumber
描述了一个元组,其起始元素是任意数量的boolean
,并以string
和number
结尾。¥
BooleansStringNumber
describes a tuple whose starting elements are any number ofboolean
s and ending with astring
then anumber
.
具有剩余元素的元组没有设置 “length” - 它只有一组不同位置的众所周知的元素。
¥A tuple with a rest element has no set “length” - it only has a set of well-known elements in different positions.
tsTry
consta :StringNumberBooleans = ["hello", 1];constb :StringNumberBooleans = ["beautiful", 2, true];constc :StringNumberBooleans = ["world", 3, true, false, true, false, true];
为什么可选的和剩余的元素可能有用?好吧,它允许 TypeScript 将元组与参数列表对应起来。元组类型可以在 剩余形参和实参 中使用,因此如下:
¥Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with parameter lists. Tuples types can be used in rest parameters and arguments, so that the following:
tsTry
functionreadButtonInput (...args : [string, number, ...boolean[]]) {const [name ,version , ...input ] =args ;// ...}
基本上相当于:
¥is basically equivalent to:
tsTry
functionreadButtonInput (name : string,version : number, ...input : boolean[]) {// ...}
当你想用一个剩余参数获取可变数量的参数时,这很方便,并且你需要最少数量的元素,但你不想引入中间变量。
¥This is handy when you want to take a variable number of arguments with a rest parameter, and you need a minimum number of elements, but you don’t want to introduce intermediate variables.
readonly
元组类型
¥readonly
Tuple Types
关于元组类型的最后一点说明 - 元组类型有 readonly
变体,可以通过在它们前面添加 readonly
修饰符来指定 - 就像数组简写语法一样。
¥One final note about tuple types - tuple types have readonly
variants, and can be specified by sticking a readonly
modifier in front of them - just like with array shorthand syntax.
tsTry
functiondoSomething (pair : readonly [string, number]) {// ...}
正如你所料,TypeScript 中不允许写入 readonly
元组的任何属性。
¥As you might expect, writing to any property of a readonly
tuple isn’t allowed in TypeScript.
tsTry
functiondoSomething (pair : readonly [string, number]) {Cannot assign to '0' because it is a read-only property.2540Cannot assign to '0' because it is a read-only property.pair [0 ] = "hello!";}
在大多数代码中,元组往往被创建并保持不变,因此尽可能将类型注释为 readonly
元组是一个很好的默认设置。这一点也很重要,因为带有 const
断言的数组字面将使用 readonly
元组类型来推断。
¥Tuples tend to be created and left un-modified in most code, so annotating types as readonly
tuples when possible is a good default.
This is also important given that array literals with const
assertions will be inferred with readonly
tuple types.
tsTry
letpoint = [3, 4] asconst ;functiondistanceFromOrigin ([x ,y ]: [number, number]) {returnMath .sqrt (x ** 2 +y ** 2);}Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.2345Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.distanceFromOrigin (); point
在这里,distanceFromOrigin
从不修改其元素,但需要一个可变元组。由于 point
的类型被推断为 readonly [3, 4]
,它不会与 [number, number]
兼容,因为该类型不能保证 point
的元素不会发生修改。
¥Here, distanceFromOrigin
never modifies its elements, but expects a mutable tuple.
Since point
’s type was inferred as readonly [3, 4]
, it won’t be compatible with [number, number]
since that type can’t guarantee point
’s elements won’t be mutated.