快速而深入地了解TypeScript及其类型
by David Piepgrass
由David Piepgrass
快速而深入地了解TypeScript及其类型 (A quick yet in-depth tour of TypeScript and its types)
联合类型,泛型,JSX,类型系统漏洞等等! (Union types, generics, JSX, type system loopholes and more!)
This quick tour of TypeScript is mainly for people who have some experience with JavaScript.
快速浏览TypeScript主要是针对具有JavaScript经验的人。
I’ll explain a few surprising facts about JavaScript, too, in case you only studied something vaguely similar, like Java or C#. If you’d like to know how to set up a TypeScript project, see my previous article.
如果您仅研究了类似的东西,例如Java或C#,我还将解释一些有关JavaScript的令人惊讶的事实。 如果您想知道如何设置TypeScript项目,请参阅我的上一篇文章 。
TypeScript is based on JavaScript. The TypeScript compiler (or other tools based on it, like ts-node
or ts-jest
) translates TypeScript into normal JavaScript simply by stripping out all the type information.
TypeScript基于JavaScript。 TypeScript编译器(或其他基于它的工具,例如ts-node
或ts-jest
)只需去除所有类型信息即可将TypeScript转换为普通JavaScript。
Alongside that process, type checking is performed in order to discover type errors — mistakes you’ve made that have something to do with types. Of course, occasionally, it also complains about things you did intentionally that nevertheless broke the rules of TypeScript.
在此过程中,还执行类型检查以发现类型错误 ,这些错误是与类型有关的错误。 当然,有时候,它也会抱怨您故意违反了TypeScript的规则。
种类 (Types)
Types can be attached to variables with a colon (:) in their definition, like so:
类型可以在定义中以冒号(:)附加到变量,如下所示:
let z: number = 26;
However you often don’t have to write down the type. For example, if you write:
但是,您通常不必写下类型。 例如,如果您编写:
let z = 26;
TypeScript infers that z is a number. So if you write:
TypeScript 推断 z是一个数字。 因此,如果您写:
let z = 26;z = "Not a number";
You’ll get an error on the second line. TypeScript originally did adopt a loophole though: any variable can be null
or undefined
:
第二行会出现错误。 TypeScript最初确实采用了漏洞:任何变量都可以为null
或undefined
:
z = null; // Allowed!z = undefined; // Allowed!
If you’re new to JavaScript, you’re probably wondering what null
and undefined
are, or why they are two different things.
如果您不熟悉JavaScript,您可能想知道null
和undefined
是什么,或者为什么它们是两个不同的东西 。
Well, I promised to tell you about TypeScript and null
/undefined
are JavaScript things. Ha!
好吧,我答应告诉您有关TypeScript和null
/ undefined
是JavaScript的事情。 哈!
Personally, I don’t use null
very much. I find it convenient to use undefined
consistently to avoid worrying about the distinction. undefined
is the default value of new variables and function parameters that were not provided by the caller. It’s the value you get if you read a property that doesn’t exist on an object. By contrast, JavaScript itself only rarely uses null
, so if you don’t use it yourself, you won’t encounter it very often. I’m sure some people do the opposite, and prefer null
.
就个人而言,我很少使用null
。 我发现方便地始终使用undefined
以避免担心区别。 undefined
是调用者未提供的新变量和函数参数的默认值。 这是您读取对象上不存在的属性时获得的值。 相比之下,JavaScript本身很少使用null
,因此,如果您自己不使用null
,则不会经常遇到它。 我敢肯定有些人会相反,并且更喜欢null
。
Anyway, some people — including me — are of the opinion that allowing every variable to be null
/undefined
was a bad idea. So TypeScript 2.0 allows you to take away that permission with the "strictNullChecks": true
compiler option in “tsconfig.json”. You can use "strict": true
for Maximum type checking. Instead, you would write:
无论如何,包括我在内的一些人认为允许每个变量为null
/ undefined
是一个坏主意。 因此,TypeScript 2.0 允许您使用"strictNullChecks": true
的"strictNullChecks": true
编译器选项来取消该权限 。 您可以使用"strict": true
进行最大类型检查。 相反,您将编写:
let z: number | null = 26;
if you want z
to be potentially null
(| means “or”).
如果您希望 z
可能为null
(|表示“或”)。
工会类型 (Union types)
TypeScript has the ability to understand variables that can have multiple types. For example, here is some normal JavaScript code:
TypeScript能够理解可以具有多种类型的变量。 例如,下面是一些普通JavaScript代码:
This is allowed in TypeScript by default, because var y
(by itself) gives y
a type of any
, meaning anything. So we can assign anything, for example value or object, to y
. We can certainly set it to a string, or a number, or an array of two things. any
is a special type — it means “this value or variable should act like a JavaScript value or variable and, therefore, not give me any type errors.”
默认情况下,这在TypeScript中是允许的,因为var y
(本身)为y
提供any
的类型,意味着任何东西。 因此,我们可以将任何值(例如value或object)分配给y
。 我们当然可以将其设置为字符串,数字或两件事情的数组。 any
是一种特殊类型-表示“此值或变量应像JavaScript值或变量一样工作,因此,不会给我任何类型错误。”
I recommend the "strict": true
compiler option. But, in that mode, TypeScript doesn’t allow var y
— it requires var y: any
instead.
我建议使用"strict": true
编译器选项。 但是,在那种模式下,TypeScript不允许var y
它需要var y: any
而是var y: any
。
However, TypeScript allows us to be more specific by saying:
但是,TypeScript通过以下方式使我们更加具体:
var y: string | number;
This means “variable y is a string or a number”. If y
is created this way, using the example above, the if-else
part is allowed. But the other part that says y = [y, y]
is not allowed, because [y, y]
is not a string and not a number either. y
is an array of type number[] | string[]
. This feature, in which a variable can have one of two (or more) types, is called union types and it’s often useful.
这意味着“变量y是字符串或数字”。 如果使用上述示例以这种方式创建y
,则允许if-else
部分。 但是不允许另一部分说y = [y, y]
,因为[y, y]
也不是字符串,也不是数字。 y
是number[] | string[]
类型的数组number[] | string[]
number[] | string[]
。 该功能(其中变量可以具有两种(或多种)类型之一)称为联合类型 ,它通常非常有用。
Tip: To help you learn TypeScript, it may help to do experiments in the playground. To help you learn more about JavaScript, press F12 in Chrome, Firefox or Edge and look for the Console. In the console you can write JavaScript code, to find out what a small piece of JavaScript does and whether you are writing it correctly:
提示:为了帮助您学习TypeScript, 在操场上进行实验可能会有所帮助。 为了帮助您了解有关JavaScript的更多信息,请在Chrome,Firefox或Edge中按F12键,然后找到控制台。 在控制台中,您可以编写JavaScript代码,以找出一小段JavaScript的功能以及是否正确编写了它:
This console is fantastic because you can use it to run experiments in any browser tab — even this one! Since TypeScript is just JavaScript with static type checking, you can use the console to help you learn about the part of TypeScript that doesn’t have static types. In your TypeScript file you can call console.log(something)
to print things in the browser’s console. In some browsers, log
can display complex objects. For example, try writing console.log({name:"Steve", age:37, favoriteNumbers:[7, 666, -1]})
:
这个控制台很棒,因为您可以使用它在任何浏览器标签中运行实验,甚至可以使用它! 由于TypeScript只是具有静态类型检查JavaScript,因此您可以使用控制台来帮助您了解TypeScript 没有静态类型的部分。 在您的TypeScript文件中,您可以调用console.log(something)
在浏览器的控制台中打印内容。 在某些浏览器中, log
可以显示复杂的对象。 例如,尝试编写console.log({name:"Steve", age:37, favoriteNumbers:[7, 666, -1]})
:
班级 (Classes)
As you know, classes are bundles of functions and variables that can be instantiated into multiple objects. Functions inside classes can refer to other functions and variables inside the class, but in JavaScript and TypeScript you must use the prefix this.
A typical JavaScript class might look like this:
如您所知,类是可以实例化为多个对象的函数和变量的捆绑。 类内的函数可以引用该类内的其他函数和变量,但是在JavaScript和TypeScript中,必须使用前缀this.
典型JavaScript类可能如下所示:
The console output is:
控制台输出为:
The big box is 10000 times larger than the small oneThe zero-size box has an area of 0.
JavaScript is a little picky. When you create a function outside a class, it has the word function
in front of it. But, when you create a function inside a class
, it is not allowed to have the word function
in front of it.
JavaScript有点挑剔。 当您在类外创建函数时,它前面会带有单词function
。 但是,当您在class
创建函数时, 不允许在其前面使用单词function
。
Functions and methods are the same thing, except that methods in classes have access to this
- a reference to the current object, except for static
methods. static
methods are called on the class
, Box.ZeroSize
in this example, so they do not have a “current object”. (Well, actually the current object of ZeroSize
is the Box
constructor function, which is not an instance of Box
.)
函数和方法是一回事,除了类中的方法可以访问this
对象(对当前对象的引用)( static
方法除外)。 在此示例中,对class
Box.ZeroSize
调用了static
方法,因此它们没有“当前对象”。 (嗯,实际上ZeroSize
的当前对象是Box
构造函数,它不是 Box
的实例。)
Unlike JavaScript, TypeScript classes allow variable declarations, such as width
and height
in this example:
与JavaScript不同,TypeScript类允许变量声明,例如本示例中的width
和height
:
For convenience, TypeScript lets you define a constructor and the variables it initializes at the same time. So instead of
为方便起见,TypeScript允许您定义构造函数及其同时初始化的变量。 所以代替
width: number; height: number; constructor(width: number, height: number) { this.width = width; this.height = height; }
you can simply write
你可以简单地写
constructor(public width: number, public height: number) {}
By the way, for any C# developers reading this, it works exactly like my LeMP system for C#.
顺便说一下,对于所有阅读此书的 C#开发人员来说,它的工作原理都与我的C# LeMP系统完全一样。
Unlike JavaScript, TypeScript has private
(and protected
) variables and functions which are inaccessible outside the class:
与JavaScript不同,TypeScript具有private
(和protected
)变量和函数,这些变量和函数在类之外是不可访问的:
private
variables allow you to clearly mark parts of a class as “internal”. Users of the class cannot modify or read these.
private
变量使您可以清楚地将类的各个部分标记为“内部”。 该类的用户无法修改或阅读这些内容。
介面 (Interfaces)
Interfaces are a way of describing “shapes” of objects. Here’s an example:
界面是描述对象“形状”的一种方式。 这是一个例子:
IBox
refers to any class that has a width
and height
property that are readable numbers. IArea
refers to anything with a readable area
property. The Box
class satisfies both of these requirements. The get area()
function counts as a property, because it is called without ()
parentheses. So I could write:
IBox
指的是具有width
和height
属性(可读数字)的任何类。 IArea
是指具有可读area
属性的任何内容。 Box
类满足这两个要求。 get area()
函数算作一个属性,因为它被调用时不带()
括号。 所以我可以写:
let a: IBox = new Box(10,100); // OKlet b: IArea = new Box(10,100); // OK
Interfaces in TypeScript work like interfaces in the Go programming language, not like interfaces in Java and C#. That’s a good thing. It means that classes don’t have to explicitly say that they implement an interface. Box
implements IBox
and IArea
without saying so.
TypeScript中的接口的工作方式类似于Go编程语言中的接口,而不是Java和C#中的接口。 这是好事。 这意味着类不必显式 说他们实现了一个接口。 Box
实现了IBox
和IArea
而无需这么说。
This means we can define interfaces for types that originally were not designed for any particular interface. For example, my BTree
package defines an IMap<Key,V
al> interface that represents a dictionary of key-value pairs. The n
ew Map class built into ES6 also conforms to this interface, so you can put
a Map into a
n IMap variable. So, for example, you can write a function with a
n IMap parameter, and you can pass
a Map or a
BTree to the function, and the function doesn’t need to know or care which type it received.
这意味着我们可以为最初不是为任何特定接口设计的类型定义接口。 例如,我的BTree
包定义了一个IMap<Key,V
al>接口,该接口代表键值对的字典。 日en
建成ES6 EW地图类也符合这个接口,所以你可以put
一个Map中to a
ñIMAP变量。 因此,举例来说,你可以写一个函数的Wi th a
ñIMAP参数,你可以p ass
一个地图or a
B树的功能,而且功能并不需要知道或关心哪种类型收到。
readonly
means we can read, but not change:
readonly
意味着我们可以阅读但不能更改:
console.log(`The box is ${a.width} by ${a.height}.`); // OKa.width = 2; /* ERR: Cannot assign to 'width' because it is a constant or a read-only property. */
TypeScript does not require readonly
for interface compatibility. For example, TypeScript accepts this code even though it doesn’t work:
TypeScript不需要readonly
实现接口兼容性。 例如,TypeScript接受以下代码,即使它不起作用:
interface IArea { area: number; // area is not readonly, so it can be changed}
let ia: IArea = new Box(10,100);ia.area = 5; // Accepted by TypeScript, but causes a runtime error
I think of it as a bug in TypeScript.
我认为这是TypeScript中的错误。
TypeScript also has a concept of optional parts of an interface:
TypeScript还具有接口的可选部分的概念:
interface Person { readonly name: string; readonly age: number; readonly spouse?: Person;}
For example we can write let p: Person = {name:'John Doe', age:37}
. Since p
is a Person
, we can later refer to p.spouse.
This is equal to undefined
in this case, but could be a Person
if a different object were assigned to it that has a spouse
.
例如,我们可以编写let p: Person = {name:'John Doe', age:37}
。 由于p
是一个Person
,我们以后可以参考p.spouse.
在这种情况下,这等于undefined
,但是如果为其分配了具有spouse
的其他对象,则可以是Person
。
However, if you use p = {name:'Chad', age:19, spouse:'Jennifer'}
with the wrong data type for spouse
, TypeScript responds that Type string is not assignable to type Person | undefined
.
但是,如果您使用p = {name:'Chad', age:19, spouse:'Jennifer'}
且spouse
数据类型有误,TypeScript响应说Type string is not assignable to type Person | undefined
Type string is not assignable to type Person | undefined
。
交叉点类型 (Intersection types)
Intersection types are the lesser-known cousin of union types. A union type like A | B
means that a value can be either an A or a B, but not both. An intersection type like A & B
means that a value is both A and B at the same time. For instance, this box
is both IBox
and IArea
, so it has all the properties from both interfaces:
交集类型是联合类型的鲜为人知的表亲。 像A | B
联合类型 A | B
装置,一个值可以是一个A或B,而不是两者。 像A & B
这样的交集类型意味着一个值同时是A和B。 例如,此box
是IBox
和IArea
,因此它具有两个接口的所有属性:
let box: IBox & IArea = new Box(5, 7);
If you mix union and intersection types, you can use parentheses to change the meaning:
如果混合并集和相交类型,则可以使用括号来更改含义:
// either a Date&IArea or IBox&IArealet box1: (Date | IBox) & IArea = new Box(5, 7);// either a Date or an IBox&IArealet box2: Date | (IBox & IArea) = new Box(5, 7);
&
has higher precedence than |
, so A & B | C
means (A & B) | C
.
&
优先级高于|
,所以A & B | C
A & B | C
表示(A & B) | C
(A & B) | C
结构类型 (Structural types)
In some other programming languages, every type has a name, such as string
or double
or Component
. In TypeScript, many types do have names but, more fundamentally, most types are defined by their structure. In other words, the type’s name, if it has one, is not important to the type system. Here’s an example where variables have a structural type:
在其他一些编程语言中,每种类型都有一个名称,例如string
或double
或Component
。 在TypeScript中,许多类型确实具有名称,但更根本地,大多数类型是由其结构定义的。 换句话说,类型名称(如果有的话)对于类型系统并不重要。 这是一个变量具有结构类型的示例:
var book1 = { title: "Adventures of Tom Sawyer", year:1876 };var book2 = { title: "Adventures of Huckleberry Finn", year:1884 };
If you hover your mouse over book1
in VS Code, its type is described as { title: string; year: number; }
. This is a structural type: a type defined entirely by the fact that it has a property called title
which is a string
, and another property called year
which is a number
. Thus book1
and book2
have the same type, and you can assign one to the other, or to a different book.
如果将鼠标悬停在VS Code中的book1
上,其类型将描述为{ title: string; year: number; }
{ title: string; year: number; }
{ title: string; year: number; }
。 这是一种结构类型:一种类型完全由以下事实定义:它具有一个称为title
的属性(它是一个string
)和另一个名为year
属性(它是一个number
。 因此book1
和book2
具有相同的类型,您可以将其中一个分配给另一个,也可以分配给另一本书。
book1 = book2; // allowedbook2 = { year: 1995, title: "Vertical Run" }; // allowed
Generally speaking, you can assign a value with “more stuff” to a variable whose type includes “less stuff”, but not the other way around:
通常,您可以将类型为“较少的东西”的变量赋值为“较多的东西”,但反之则不行:
var book3 = { title: "The Duplicate", author: "William Sleator", year:1988 };var book4 = { title: "The Boy Who Reversed Himself" };book1 = book3; // allowedbool1 = bool4; /* NOT allowed. Here is the error message: Type '{ title: string; }' is not assignable to type '{ title: string; year: number; }'. Property 'year' is missing in type '{ title: string; }'. */
In addition, if we have an interface like this:
另外,如果我们有这样的接口:
interface Book { title: string; author?: string; year: number;}
Then we can assign any Book
value to either book1
or book2
. But author
is required in book3
and Book
might not contain an author. We can assign any of the book variables to a new variable of type Book
, except book4
, of course.
然后,我们可以将任何Book
值分配给book1
或book2
。 但是book3
需要author
,并且Book
可能不包含作者。 当然,我们可以将任何book变量分配给Book
类型的新变量, book4
除外。
Clearly, structural types are fantastic. This is obvious after you spend a few years using languages without them. For example, imagine if two people, Alfred and Barbara, write different modules A
and B
. They both deal with points using X-Y coordinates. So each module contains a Point
interface:
显然,结构类型很棒。 在花了几年时间使用没有语言的语言之后,这一点显而易见。 例如,假设两个人Alfred和Barbara编写了不同的模块A
和B
它们都使用XY坐标处理点。 因此,每个模块都包含一个Point
接口:
interface Point { x: number; y: number;}
Many languages use nominal types instead of structural types. In these languages, A.Point
is considered to be a completely different type than B.Point
even though they are identical. So any points produced by A
cannot be used by B
and vice versa. This can be frustrating, so please take a moment to celebrate with me the wonder of TypeScript’s structural typing.
许多语言使用名义类型而不是结构类型。 在这些语言中, A.Point
被认为是一个完全不同的类型B.Point
即使它们是相同的。 因此, A
产生的任何点都不能被B
使用,反之亦然。 这可能令人沮丧,所以请花一点时间与我一起庆祝TypeScript的结构化打字的奇迹。
Structural types can be written either with semicolons or commas, e.g. { x: number, y: number }
and { x: number; y: number; }
are the same.
结构类型可以用分号或逗号来写,例如{ x: number, y: number }
和{ x: number; y: number; }
{ x: number; y: number; }
{ x: number; y: number; }
是相同的。
基于流的键入和感叹号 (Flow-based typing and the exclamation mark)
If s
is a string, you could write s.match(/[0-9]+/)
to find the first group of digits in that string. /[0-9]+/
is a RegExp
- an object that can be used to search strings using Regular Expressions. Regular expressions are a string-matching system supported by many programming languages, including JavaScript.
如果s
是一个字符串,则可以编写s.match( /[0-9]+/ )
以查找该字符串中的第一组数字。 /[0-9]+/
是RegExp
一个对象,可用于使用正则表达式搜索字符串。 正则表达式是许多编程语言(包括JavaScript)支持的字符串匹配系统。
match
returns an array of strings, or null
if the RegExp
did not match the string. For example, if s = "I have 10 cats and 2 dogs"
then s.match(/[0-9]+/)
returns ["10"]
, but if s = "I have ten velociraptors and a weevil"
then match
returns null
.
match
返回字符串数组;如果RegExp
与字符串不匹配,则返回null
。 例如,如果s = "I have 10 cats and 2 dogs"
则s.match(/[0-9]+/)
返回["10"]
,但是如果s = "I have ten velociraptors and a weevil"
则match
返回null
。
If you were looking for digits in a string, you’d want your code to behave differently depending on whether the string has digits or not, right? So you’d use an if
statement:
如果要在字符串中查找数字,则希望代码根据字符串是否包含数字而表现不同,对吗? 因此,您将使用if
语句:
var found: string[]|null = s.match(/[0-9]+/);if (found) { console.log("The string has a number in it: " + found[0]);} else { console.log("The string lacks digits.");}
As you probably know, if (found)
means “if found is truthy”. It basically means if (found != null && found != 0 && found != false)
.
如您所知, if (found)
意思是“ if found is true”。 它基本上表示if (found != null && found != 0 && found != false)
。
If you don’t check whether found !== null
, TypeScript will give you an error:
如果您不检查是否found !== null
,TypeScript将给您一个错误:
var found = s.match(/[0-9]+/);console.log("The string has a number in it: " + found[0]); // Error: Object is possibly 'null' ^^^^^
So why don’t you get an error when you use the if
statement? That’s the magic of TypeScript’s flow-based typing.
那么,为什么在使用if
语句时不出现错误? 这就是TypeScript基于流的键入的魔力。
In the first branch of the if
statement, TypeScript knows that found
cannot be null, and so the type of found
changes within that block to exclude null
. Thus, its type becomes string[]
. Similarly, inside the else {...}
block, TypeScript knows that found
cannot be string[]
, so string[]
is excluded and the type of found
becomes null
in that region.
在第一分院if
声明,打字稿知道found
不能为空,所以类型found
该块内的变化 ,以排除null
。 因此,其类型变为string[]
。 类似地,在else {...}
块中,TypeScript知道found
不能为string[]
,因此排除string[]
且在该区域中found
的类型变为null
。
But TypeScript has a !
operator which is used to avoid certain error messages. It means “look, compiler, I know you think this variable could be null
or undefined
, but I promise you it isn’t. So if found
has type string[]|null
, thenfound!
has type string[]
.”
但是TypeScript有一个!
用于避免某些错误消息的运算符。 它的意思是“看,编译器,我知道您认为此变量可以为null
或undefined
,但我向您保证不是。 因此,如果found
类型为string[]|null
,则found!
的类型为string[]
。”
If you’re sure that s
has digits in it, you can use !
to avoid the error message:
如果确定s
包含数字,则可以使用!
为了避免出现错误信息:
var found = s.match(/[0-9]+/);console.log("The string has a number in it: " + found![0]);
TypeScript’s flow-based typing system supports the typeof
and instanceof
operators, as well as ordinary comparison operators. If you start with a variable that could have several types, you can use any of these operators to narrow down the type:
TypeScript的基于流的键入系统支持typeof
和instanceof
运算符以及普通的比较运算符。 如果从可能具有几种类型的变量开始,则可以使用以下任何一种运算符来缩小类型的范围:
Note: JavaScript distinguishes between primitive and boxed primitive types, which are objects. For example, "yarn"
is a primitive, and its type is string
. However, there is also a boxed string type called String
with a capital S, which is rarely used. You can create a String
by writing new String("yarn")
. The thing to keep in mind is that these are totally different types.
注:JavaScript的原始和装箱的基本类型,这是对象之间进行区分。 例如, "yarn"
是一个原始类型,其类型是string
。 但是,也有一种装箱的字符串类型,称为String
,其首字母为S,很少使用。 您可以通过编写new String("yarn")
创建一个String
。 要记住的是,这些是完全不同的类型。
"yarn" instanceof String
is false
: "yarn"
is a string
, not a String
!
"yarn" instanceof String
是false
: "yarn"
是一个string
,而不是String
!
"yarn" instanceof string
is not false. Instead it’s a totally illegal expression — the right-hand side of instanceof
must be a constructor function and string
does not have a constructor.
"yarn" instanceof string
不是 false。 相反,它是一个完全合法的表达-的右侧instanceof
必须是一个构造函数和string
没有一个构造函数。
JavaScript provides two different operators for testing the types of primitives and objects (non-primitives):
JavaScript提供了两种不同的运算符来测试基元和对象(非基元)的类型:
instanceof
checks the prototype chain to find out if a value is a certain kind of object.instanceof
检查原型链,以确定值是否是某种对象。typeof
checks whether something is a primitive and if so, what kind.typeof
检查某物是否为原始类型,如果是,则为哪种类型。
As you can see in the code above, instanceof
is a binary operator that returns a boolean, while typeof
is a unary operator that returns a string. For example, typeof "yarn"
returns "string"
and typeof 12345
returns "number"
. The primitive types are number
, boolean
, string
, symbol
, undefined
, and null
. Everything that is not a primitive is an Object
, including functions.
如您在上面的代码中看到的, instanceof
是一个返回布尔值的二进制运算符,而typeof
是一个返回字符串的一元运算符。 例如, typeof "yarn"
返回"string"
, typeof 12345
返回"number"
。 基本类型为number
, boolean
, string
, symbol
, undefined
和null
。 所有不是原始的东西都是Object
,包括函数。
But typeof
treats functions specially. For example, typeof Math.sqrt === "function"
, and Math.sqrt instanceof Object === true
. Symbols are new in ES6 and, although null
is a primitive, typeof null === "object"
is a mistake.
但是typeof
特别对待功能。 例如, typeof Math.sqrt === "function"
和Math.sqrt instanceof Object === true
。 符号在ES6中是新的,尽管null
是原始类型,但typeof null === "object"
是一个错误 。
As you can see in the example above, TypeScript also understands Array.isArray
as a way to detect an array. However, some other methods of detecting types in JavaScript are not supported:
如您在上面的示例中看到的,TypeScript也将Array.isArray
理解为检测数组的一种方法。 但是,不支持其他一些检测JavaScript类型的方法:
if (thing.unshift)
is sometimes used to distinguish strings from other things, because almost nothing except strings have anunshift
method. This is not supported in TypeScript because it does not let you read a property that may not exist.if (thing.unshift)
有时用于将字符串与其他事物区分开,因为除字符串外,几乎没有其他东西具有unshift
方法。 TypeScript不支持此功能,因为它不允许您读取可能不存在的属性。if (thing.hasOwnProperty("unshift"))
isn’t recognized as a type test.if (thing.hasOwnProperty("unshift"))
没有被识别为类型测试。if (thing.constructor === String)
isn’t recognized as a type test. In JavaScript, reading a property such asconstructor
promotesthing
to Boxed status, so even ifthing
is a primitive string, its.constructor
will be non-primitive.if (thing.constructor === String)
不被识别为类型测试。 在JavaScript中,读出的属性,如constructor
促进thing
到盒装状态,所以即使thing
是一种原始的字符串 ,其.constructor
将非基本 。if ("unshift" in thing)
doesn’t work. “The right-hand side of an ‘in’ expression must be of type ‘any’, an object type or a type parameter.” (in
should be avoided anyway because it is slow.)if ("unshift" in thing)
不变if ("unshift" in thing)
不起作用。 “'in'表达式的右侧必须是'any'类型,对象类型或类型参数。” (因为速度太慢,无论如何都应避免in
。)
类型别名 (Type aliases)
The type
statement creates a new name for a type. For example after writing:
type
语句为类型创建一个新名称。 例如写后:
type num = number;
You can use num
as a synonym for number
. type
is similar to interface
since you can write something like this…
您可以将num
用作number
的同义词。 type
类似于interface
因为您可以编写如下内容…
type Point = { x: number; y: number;}
…instead of interface Point {...}
. However, only interfaces support inheritance. For example I can create a new interface that is like Point
but also has a new member z
, like this:
…代替interface Point {...}
。 但是,仅接口支持继承。 例如,我可以创建一个像 Point
这样的新接口,但也有一个新成员z
,如下所示:
interface Point3D extends Point { z: number;}
You can’t do inheritance with type
. However if Point
was defined with type
, you are still allowed to extend it with an interface
.
您不能使用type
继承。 但是,如果Point
是用type
定义的,则仍然可以使用interface
对其进行扩展。
功能类型 (Function types)
In JavaScript you can pass functions to other functions, like this:
在JavaScript中,您可以将函数传递给其他函数,如下所示:
function doubler(x) { return x*2; }function squarer(x) { return x*x; }function experimenter(func){ console.log(`When I send 5 to my function, I get ${func(5)}.`);}experimenter(doubler);experimenter(squarer);
Output:
输出:
When I send 5 to my function, I get 10.When I send 5 to my function, I get 25.
In TypeScript you normally need to write down the types of function arguments — you need to know how to express the type of func
. As you can see here, its type should be something like (param: number) => num
ber:
在TypeScript中,通常需要写下函数参数的类型-您需要知道如何表达func
的类型。 如您所见,其类型应类似于(param: number) => num
ber:
function doubler(x: number) { return x*2; }function squarer(x: number) { return x*x; }function experimenter(func: (param: number) => number){ console.log(`When I send 5 to my function, I get ${func(5)}.`);}experimenter(doubler);experimenter(squarer);
TypeScript requires you to give a name to the parameter of func
, but it doesn’t matter what that name is. I could have called it x
, or Wednesday
, or myFavoriteSwearWord
and it would have made no difference whatsoever. But don’t even think of calling it asshat
. The compiler won’t care, but what about your boss? Better safe than sorry, that’s all I can say.
TypeScript要求您为func
的参数命名 ,但是该名称是什么都没有关系。 我可以把它叫做x
,或Wednesday
,或myFavoriteSwearWord
,它将会任何没有区别。 但是,甚至不要把它称为asshat
。 编译器不在乎,但是您的老板呢? 我能说的比对不起要好。
In JavaScript, everything inside an object is a property — a kind of variable — and that includes functions. As a consequence, these two interfaces mean the same thing:
在JavaScript中,对象内部的所有内容都是属性(一种变量),并且包含函数。 结果,这两个接口具有相同的含义:
interface Thing1 { func: (param: number) => number;}interface Thing2 { func(param: number): number;}
And so this code is allowed:
因此,可以使用以下代码:
class Thing { func(x: number) { return x * x * x; }}let t1: Thing1 = new Thing();let t2: Thing2 = t1;
Does it seem weird to you that TypeScript requires :
before the return type of a “normal” function but it requires =&
gt; before the return type of a function variable? Anyway, that’s the way it is.
对您来说,TypeScript要求看起来是否很奇怪:
在“正常”函数的返回类型之前但它需要=&
gt; 在函数变量的返回类型之前? 无论如何,就是这样。
泛型,日期和内容 (Generics, and dates, and stuff)
日期 (Dates)
Let’s say I write a function that ensures a value is an array, like this:
假设我编写了一个确保值是数组的函数,如下所示:
function asArray(v: any): any[] { // return v if it is an array, otherwise return [v] return (Array.isArray(v) ? v : [v]);}
The asArray
function works, but it loses type information. For example, what if this function calls it?
asArray
函数可以工作,但是会丢失类型信息。 例如,如果该函数调用该怎么办?
/** Prints one or more dates to the console */function printDates(dates: Date|Date[]) { for (let date of asArray(dates)) { // SUPER BUGGY! var year = date.getYear(); var month = date.getMonth() + 1; var day = date.getDay(); console.log(`${year}/${month}/${day}`); }}
The TypeScript compiler accepts this code, but it has two bugs. The code correctly added 1
to the month, because getMonth()
returns 0 for January and 11 for December. But the code for getting the year
and day
are both wrong. Since asArray
returns any[]
, however, type checking and IntelliSense — which could have caught these bugs — is disabled on date
. These bugs could have been avoided if asArray
was generic:
TypeScript编译器接受此代码,但是有两个错误。 该代码正确地向月份添加了1
,因为getMonth()
对于1月返回0,对于12月返回11。 但是获取year
和day
的代码都是错误的。 由于asArray
返回any[]
,因此类型检查和IntelliSense(可能已捕获这些错误)在date
上被禁用。 如果asArray
是通用的,则可以避免这些错误:
function asArray<T>(v: T | T[]): T[] { return Array.isArray(v) ? v : [v];}
This version of asArray
does the same thing, but it has a type parameter, which I have decided to call T
, to enable enhanced type checking. The type parameter can be any type, so it is similar to any
. But it enables the function to describe the relationship between the parameter v
and the return value.
这个版本的asArray
做相同的事情,但是它具有类型参数 ,我决定将其称为T
,以启用增强的类型检查。 type参数可以是任何类型,因此类似于any
。 但是,它使函数能够描述参数v
和返回值之间的关系 。
Specifically, it says that v
and the return value have, well, similar types. When you call asArray
, the TypeScript compiler finds a value of T
that allows the call to make sense. For example, if you call asArray(42)
then the compiler chooses T=number
because it is possible to use 42 as an argument to asArray(v: number|number[]): number[]
. After choosing T=number
, TypeScript realizes that asArray
returns an array of numbers.
具体来说,它表示v
和返回值具有相似的类型。 调用asArray
,TypeScript编译器会找到T
值,该值使调用有意义。 例如,如果调用asArray(42)
则编译器选择T=number
因为可以将42用作asArray(v: number|number[]): number[]
。 选择T=number
,TypeScript意识到asArray
返回一个数字数组。
In printDates
we called asArray(dates)
and the compiler figures out that T=Date
works best in that situation. After choosing T=Date
, TypeScript realizes that asArray
returns an array of Date
. Therefore, the variable date
has type Date
, and then it finds the first bug: date.getYear
does not exist! Well, actually it does exist, but it has been deprecated due to its behavior — it returns the number of years since 1900 — 118 in 2018. Instead, you should call getFullYear
.
在printDates
我们称为asArray(dates)
,编译器认为T=Date
在这种情况下效果最好。 选择T=Date
,TypeScript意识到asArray
返回一个Date
数组。 因此,变量date
类型为Date
,然后发现第一个错误: date.getYear
不存在! 好吧,实际上它确实存在,但是由于它的行为而被弃用了-它返回自1900年以来的年数-2018年为118年。相反,您应该调用getFullYear
。
TypeScript itself doesn’t notice the second bug. But, when you type date.getDay
, VS Code will inform you in a little popup box that this function “Gets the day of the week, using local time”. The day of the week? You have got to be kidding me!
TypeScript本身不会注意到第二个错误。 但是,当您键入date.getDay
,VS Code将在一个小的弹出框中通知您此函数“使用本地时间获取星期几”。 星期几? 你一定是在跟我开玩笑!
Thanks to generics and VS Code, we fix our code to call date.getDate
instead. This does not return the date without a time attached to it but, rather, the day of the current month. Unlike the month, the day does not start counting from zero.
多亏了泛型和VS Code,我们将代码固定为调用date.getDate
。 这并不不附加给它的时间返回的日期,而是当月的一天 。 与月份不同,日期并非从零开始计数。
/** Prints one or more dates to the console */function printDates(dates: Date|Date[]) { for (let date of asArray(dates)) { var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); console.log(`${year}/${month}/${day}`); }}
One good thing about Date
is that they are normally stored in UTC — universal time zone, or GMT. This means that if the user changes the time zone on their computer, the Date
objects in your program continue to represent the same point in time, but the string returned by .toString()
changes. Usually this is what you want, especially in JavaScript where you might have client and server code running in different time zones.
关于Date
一件好事是它们通常存储在UTC(世界时区,即GMT)中。 这意味着,如果用户更改计算机上的时区,则程序中的Date
对象将继续表示相同的时间点 ,但是.toString()
返回的字符串会更改。 通常这就是您想要的,尤其是在JavaScript中,您的客户端和服务器代码可能在不同的时区运行。
泛型 (Generics)
An advanced example of generics appears in my simplertime module. In this case I had a timeToString
function that accepted a list of formatting options like this:
我的simplertime模块中提供了泛型的高级示例。 在这种情况下,我有一个timeToString
函数,它接受如下格式的列表:
export interface TimeFormatOptions { /** If true, a 24-hour clock is used and AM/PM is hidden */ use24hourTime?: boolean; /** Whether to include seconds in the output (null causes seconds * to be shown only if seconds or milliseconds are nonzero) */ showSeconds?: boolean|null; ...}
export function timeToString(time: Date|number, opt?: TimeFormatOptions): string { ...}
The export
keyword is used for sharing code to other source files. For example you can import timeToString
in your own code using import {timeToString} from 'simplertime'
(after installing with npm i simplertime
of course). If you want to import things from a different file in the same folder, add a ./
prefix on the filename, e.g. import * as stuff from './mystuff'
.
export
关键字用于与其他源文件共享代码。 例如,你可以导入timeToString
在自己的代码使用import {timeToString} from 'simplertime'
(后安装npm i simplertime
当然)。 如果要从同一文件夹中的其他文件导入内容,请在文件名上添加./
前缀,例如import * as stuff from './mystuff'
。
Generics can also be used on classes and interfaces. For example, JavaScript has a Set
type for holding an unordered collection of values. We might use it like this:
泛型也可以在类和接口上使用。 例如,JavaScript具有Set
类型,用于保存值的无序集合。 我们可以这样使用它:
var primes = new Set([2, 3, 5, 7]);for (var i = 0; i < 10; i++) console.log(`Is the number ${i} prime? ${primes.has(i)}`);
In TypeScript, though, Set
has a type parameter, Set<
;T>, meaning that all items in the set have type T. In this code TypeScript infers that T=
number, so if you write primes.add("he
llo!") you’ll get a Type Error. If you actually want to create a set that can hold both strings and numbers, you can do it like this:
但是,在TypeScript中, Set
具有类型参数Set<
; T>,这意味着该集合中的所有项目都具有 类型T。在此代码中,TypeScript推断that T=
数字,因此,如果您write primes.add("he
llo!”),则会出现类型错误。 如果你的行为uallŸ要创建一组可容纳两个字符串和数字,你可以做这样的:
var primes = new Set<string|number>([2, 3, 5, 7]);
You can also create your own generic types. For example, I created a B+ Tree data structure called BTree<K,
V>, which is a collection of key-value pairs, sorted by key, that supports fast cloning. It has two type paramet
ers, K (a key)
and V (a value) and its definition looks roughly like this. Note: function bodies have been omitted because I just want to show you how a generic class looks:
您也可以创建自己的泛型类型。 例如,我创建了一个名为BTree<K,
V>的B + Tree数据结构,该结构是键-值对的集合,按键排序,支持快速克隆。 它有两个类型parame t
ERS,K(键)
和V(值)和它的定义看起来大致是这样的。 注意:函数主体已被省略,因为我只想向您展示泛型类的外观:
文字作为类型 (Literals as types)
Remember how there is an error when you write this?
还记得写这篇文章时怎么会出错吗?
let z = 26;z = "Zed";
The error message sounds a bit strange:
错误消息听起来有点奇怪:
Type '"Zed"' is not assignable to type 'number'
Why does it say that "Zed"
is a “type”, instead of a “value” or a “string”? In order to understand this, it is necessary to understand that TypeScript has an ability to treat values as types. "Zed"
is a string
, of course, but it’s more than that — it has another type at the same time, a more specific type called "Zed"
which represents the value "Zed"
. We can even create a variable with this type:
为什么说"Zed"
是“类型”,而不是“值”或“字符串”? 为了理解这一点,有必要了解TypeScript具有将值视为类型的能力。 当然, "Zed"
是一个string
,但不仅限于此-它同时具有另一种类型 ,一个更具体的类型称为"Zed"
,它表示值 "Zed"
。 我们甚至可以创建这种类型的变量:
let zed: "Zed" = "Zed";
Now we have created a completely useless variable called zed
. We can set this variable to "Zed"
, but nothing else:
现在,我们创建了一个完全无用的变量zed
。 我们可以将此变量设置为"Zed"
,但除此之外:
zed = "Zed"; // OKzed = "ZED"; // Error: Type '"ZED"' is not assignable to type '"Zed"'.
By default we can set zed
to null
and undefined.
Luckily with the "strictNullChecks": true
option, we can close that loophole so that this variable will never be anything except “Zed”. Thank God for that, is all I can say.
默认情况下,我们可以将zed
设置为null
和undefined.
幸运的是,使用"strictNullChecks": true
选项,我们可以关闭该漏洞,从而使该变量永远不会是“ Zed”以外的任何值。 我只能说感谢上帝。
So what are these literal-types good for? Well, sometimes a function allows only certain particular strings. For example, imagine if you have a function that lets you turn("left")
or turn("right")
but nothing else. This function could be declared with a literal-type:
那么这些文字类型有什么用呢? 好吧,有时一个函数只允许某些特定的字符串。 例如,假设您有一个可以让turn("left")
或turn("right")
但没有其他功能的函数。 可以使用文字类型声明此函数:
function turn(direction: "left"|"right") { … }
定长数组 (Fixed-length arrays)
Here’s another puzzle for you: what’s the difference between the types number[]
and [number]
? The first is an array of numbers, the second is an array that contains only one element, which is a number.
这是另一个难题:类型number[]
和[number]
什么区别? 第一个是数字数组,第二个是仅包含一个元素(数字)的数组。
Similarly [string,number]
denotes an array of length 2 with the first element being a string and the second being a number. In addition, the array has a property length: 2
, i.e. its type is 2
, not just number
. These fixed-length arrays are called tuple types.
同样, [string,number]
表示长度为2的数组,第一个元素是字符串,第二个元素是数字。 另外,数组的属性length: 2
,即其类型为2
,而不仅仅是number
。 这些固定长度的数组称为元组类型。
高级仿制药 (Advanced generics)
So, remember the simplertime
module I was talking about? It also exports a defaultTimeFormat
object which holds default values for the timeToString
formatting options. I wanted to define a special function which would allow me to write things like get(options, 'use24hourTime')
to retrieve the value of options.use24hourTime
if it exists and defaultTimeFormat.use24hourTime
if it does not exist.
所以,还记得我在说的更simplertime
模块吗? 它还会导出defaultTimeFormat
对象,该对象保存timeToString
格式设置选项的默认值。 我想定义一个特殊的函数,使我可以编写诸如get(options, 'use24hourTime')
来检索options.use24hourTime
的值(如果存在)和defaultTimeFormat.use24hourTime
如果不存在)。
In many languages it is impossible to write a function like that, but it is possible in “dynamic” languages such JavaScript. Here’s how the get
function would look like in JavaScript:
在许多语言中,不可能编写这样的函数,但是在诸如JavaScript的“动态”语言中,这是可能的。 以下是get
函数在JavaScript中的外观:
function get(opt, name) { if (opt === undefined || opt[name] === undefined) return defaultTimeFormat[name] return opt[name];}
In JavaScript and TypeScript, thing.property
can be written as thing["property"]
instead and, if the property does not exist, the result is undefined
. But in the square-bracket version we can use a variable, so that the question “which property are we using?” can be answered by code located elsewhere.
在JavaScript和TypeScript中, thing.property
可以写为thing["property"]
并且,如果该属性不存在,则结果是undefined
。 但是在方括号版本中,我们可以使用一个变量 ,这样的问题是“我们正在使用哪个属性?” 可以通过其他地方的代码来回答。
Translating this to TypeScript is possible with a feature called keyof
, but it’s very tricky. Here is the translation:
可以使用称为keyof
的功能将keyof
转换为TypeScript,但这非常棘手。 这是翻译:
function get<;K extends keyof TimeFormatOptions>( opt: TimeFormatOptions|undefined, name: K): TimeFormatOptions[K]{ if (opt === undefined || opt[name] === undefined) return defaultTimeFormat[name] return opt[name];}
Here, the type variable K
has a constraint attached to it, K extends keyof TimeFormatOptions
. Here’s how it works:
在此,类型变量K
附加有约束 , K extends keyof TimeFormatOptions
。 运作方式如下:
keyof X
turns the properties ofX
into a union type of the names of the properties. For example, given theBook
interface from earlier,keyof Book
means"title" | "author" | "age"
. Likewisekeyof TimeFormatOptions
is any of the property names inTimeFormatOptions
.keyof X
打开的属性X
成联合类型属性的名称。 例如,给定较早版本的Book
界面,keyof Book
表示"title" | "author" | "age"
"title" | "author" | "age"
"title" | "author" | "age"
。 同样keyof TimeFormatOptions
是任何在属性名的TimeFormatOptions
。The “extends” constraint,
X extends Y
, means that “X must be Y, or a subtype of Y”. For exampleX extends Object
means thatX
must be some kind ofObject
, which means it can be an array or aDate
or even a function, all of which are considered to be Objects, but it can’t be astring
or anumber
or aboolean
. SimilarlyX extends Point
means thatX
isPoint
or a more specific type thanPoint
, such asPoint3D
.“扩展”约束
X extends Y
,表示“ X必须是Y或Y的子类型”。 例如X extends Object
意味着X
必须是某种Object
,这意味着它可以是数组或Date
甚至是一个函数,所有这些都被视为Object,但不能是string
或number
或boolean
。 类似地,X extends Point
意味着X
是Point
或比Point
更特定的类型,例如Point3D
。What would
B extends keyof Book
mean? It would mean thatB
is a subtype of"title" | "author" | "age"
. And, remember, that we are talking about types here, not values. The string literal"title"
has the value"title"
but it also has the type"title"
, which is a different concept. The type is handled by the TypeScript type system, and the value is handled by the JavaScript. The"title"
type no longer exists when the program is running, but the"title"
value still does. Now,B
can be assigned to types like"title"
or"title" | "age"
, because every value of type"title" | "age"
(or"title"
) can be assigned to a variable of typekeyof Book
. HoweverB
cannot bestring
, because some strings are not “title”, “author”, or “age”.B extends keyof Book
含义是什么? 这意味着B
是"title" | "author" | "age"
的子类型 。"title" | "author" | "age"
"title" | "author" | "age"
。 而且,请记住,我们在这里谈论的是类型 , 而不是值。 字符串文字"title"
的值为"title"
但其类型也为"title"
,这是一个不同的概念。 该类型由TypeScript类型系统处理,而值由JavaScript处理。 程序运行时,"title"
类型不再存在,但"title"
值仍然存在。 现在,可以将B
分配给"title"
或"title" | "age"
"title" | "age"
,因为类型为"title" | "age"
每个值"title" | "age"
"title" | "age"
(或"title"
)分配给keyof Book
类型的变量。 但是B
不能为string
,因为某些字符串不是“ title”,“ author”或“ age”。Similarly,
K
is constrained to have a subtype ofkeyof TimeFormatOptions
, such as"use24hourTime"
.同样,
K
被约束为具有keyof TimeFormatOptions
的子类型,例如"use24hourTime"
。The type
X[Y]
means “the type of the Y property of X, where Y is a number or string literal”. For example, the typeBook["author"]
isstring | undefined
.X[Y]
类型的意思是“ X的Y属性的类型,其中Y是数字或字符串文字”。 例如, 类型Book["author"]
是string | undefined
string | undefined
。
Putting this all together, when I write get(options, 'use24hourTime')
, the compiler decides that K='use24hourTime'
. Therefore, the name
parameter has type "use24hourTime"
and the return type is TimeFormatOptions["use24hourTime"]
, which means boolean | undefined
.
get(options, 'use24hourTime')
,当我编写get(options, 'use24hourTime')
,编译器会确定K='use24hourTime'
。 因此, name
参数的类型为"use24hourTime"
,返回类型为TimeFormatOptions["use24hourTime"]
,表示boolean | undefined
boolean | undefined
。
类型系统中的Kong (Holes in the type system)
Since TypeScript is built on top of JavaScript, it accepts some flaws in its type system for various reasons. Earlier we saw one of these flaws, the fact that this code is legal:
由于TypeScript基于JavaScript构建,因此由于各种原因,它在其类型系统中存在一些缺陷。 早些时候,我们看到了这些缺陷之一,即该代码是合法的:
class Box { constructor(public width: number, public height: number) {} get area() { return this.width*this.height; }}
interface IArea { area: number; // area is not readonly}
let ia: IArea = new Box(10,100);ia.area = 5; // Accepted by TypeScript, but causes a runtime error
Here are some other interesting loopholes:
以下是一些其他有趣的漏洞:
您可以将派生类分配给基类 (You can assign a derived class to a base class)
A Date
is a kind of Object
so naturally you can write:
Date
是一种Object
因此自然可以编写:
var d: Object = new Date();
So it makes sense that we can also assign this D
interface to this O
interface, right?
因此,我们也可以将此D
接口分配给该O
接口是对的,对吗?
interface D { date: Date }interface O { date: Object }var de: D = { date: new Date() }; // okay...var oh: O = de; // makes sense...oh.date = { date: {wait:"what?"} } // wait, what?
Well, no, not really, because TypeScript now believes de.date
is a Date
when it is actually an Object
.
好吧,不是,不是真的,因为TypeScript现在认为de.date
实际上是一个Object
,它是一个Date
。
您可以将[A,B]分配给(A | B)[] (You can assign [A,B] to (A|B)[])
It makes sense that an array of two items, an A
followed by a B
, is also a an array of A|B
, right? Actually, no, not really:
有意义的是,两个项的数组A
后面跟着B
,也是A|B
的数组,对吗? 实际上,不,不是真的:
var array1: [number,string] = [5,"five"];var array2: (number|string)[] = array1; // makes sense...array2[0] = "string!"; // wait, what?
TypeScript now believes array1[0]
is a number
when it is actually a string
. This is an example of a more general problem, that arrays are treated as covariant but they aren’t really covariant because they are editable.
打字稿现在认为array1[0]
是一个number
时,它实际上是一个string
。 这是一个更普遍的问题的示例,该数组被视为协变的,但它们并不是真正的协变,因为它们是可编辑的。
数组? 有龙。 (Arrays? There be dragons.)
In the recommended strict
mode, you can’t put null
or undefined
in arrays of numbers…
在推荐的strict
模式下,您不能在数字数组中放置null
或undefined
…
var a = [1,2,3];a[3] = undefined; // 'undefined' is not assignable to type 'number'
So that means when we get a value from an array of numbers, it’s a number, right? Actually, no, not really:
所以这意味着当我们从数字数组中获得一个值时,它就是一个数字,对吗? 实际上,不,不是真的:
var array = [1,2,3];var n = array[4];
TypeScript now believes n
is a number
when it is actually undefined
.
打字稿现在相信n
是一个number
时,它实际上是undefined
。
A more obvious hole is that you can allocate a sized array of numbers… with no numbers in it:
一个更明显的漏洞是您可以分配一个大小合适的数字数组……其中没有数字:
var array = new Array<number>(2); // array of two "numbers"var n:number = array[0];
覆盖时函数参数是双变量的 (Function parameters are bivariant when overriding)
Unlike other languages with static typing, TypeScript allows overriding with covariant parameters. Covariant parameter means that, as the class gets more specific (A to B), the parameter also gets more specific (Object to Date):
与其他具有静态类型的语言不同,TypeScript允许使用协变参数覆盖。 协变量参数表示,随着类变得更加具体(从A到B),该参数也变得更加具体(从Object到Date):
class A { method(value: Object) { }}
class B extends A { method(value: Date) { console.log(value.getFullYear()); }}
var a:A = new B();a.method({}); // Calls B.method, which has a runtime error
This is unsafe, but oddly it is allowed. In contrast, it is (relatively) safe to override with contravariant parameters, like this:
这是不安全的,但是奇怪的是它是允许的。 相反,它是(相对)安全与逆变参数,这样的控制装置:
class A { method(value: Date) { }}class B extends A { method(value: Object) { console.log(value instanceof Date); }}
Covariant return types are also safe:
协变返回类型也是安全的:
class A { method(): Object { return {} }}class B extends A { method(): Date { return new Date(); }}
TypeScript rightly rejects contravariant return types:
TypeScript正确拒绝协变返回类型:
class A { method(): Date { return new Date(); }}class B extends A { // Property 'method' in type 'B' is not assignable to // the same property in base type 'A'. // Type '() => Object' is not assignable to type '() => Date' // Type 'Object' is not assignable to type 'Date' method(): Object { return {} }}
类认为它们是接口(但不是) (Classes think they are interfaces (but they’re not))
TypeScript allows you to treat a class as though it were an interface. For example, this is legal:
TypeScript允许您将类视为接口。 例如,这是合法的:
class Class { content: string = "";}
var stuff: Class = {content:"stuff"};
Stuff isn’t a real Class
, but TypeScript thinks it is, which can cause a runtime TypeError
if you use instanceof Class
somewhere else in the program:
Stuff不是真正的Class
,但是TypeScript认为它是真实的,如果您在程序中的其他地方使用instanceof Class
,则可能导致运行时TypeError
:
function typeTest(x: Class|Date) { if (x instanceof Class) console.log("The class's content is " + x.content); else console.log("It's a Date in the year " + x.getFullYear());}
typeTest(stuff);
this
不一定是你的想法 (this
isn’t necessarily what you think)
this
is a loophole of JavaScript, not TypeScript. Any time a function uses this
, it might be accessing some completely unexpected object, with a different type than you think:
this
是JavaScript的漏洞,而不是TypeScript。 每当函数使用this
,它可能会访问某种完全意外的对象,其类型与您想象的类型不同:
class Time { constructor(public hours: number, public minutes: number) { } toDate(day: Date) { var clone = new Date(day); clone.setHours(this.hours, this.minutes); return clone; }}
// Call toDate() with this=12345Time.prototype.toDate.call(12345, new Date());
TypeScript’s only sin is that it won’t try to stop you from doing this.
TypeScript的唯一罪过是它不会试图阻止您这样做。
Speaking of this
, one thing JavaScript developers should know is that arrow functions like x =>
x+1 work slightly differently than anonymous functions like function(x) {return x
+1}.
说到this
,JavaScript开发人员应该知道的一件事是, x =>
x + 1之类的箭头函数与匿名ke function(x) {return x
+1} ke function(x) {return x
工作方式略有不同。
Arrow functions inherit the value of this
from the outer function in which they are located. Normal functions receive a new value of this
from the caller. So, if f
is an arrow function, f.call(12345, x)
doesn’t change this
, so it’s like calling f(x)
. That’s usually a good thing, but if you write:
箭头的函数继承的值this
从它们所在的外部函数。 正常功能得到的新值this
从主叫方。 因此,如果f
是箭头函数,则f.call(12345, x)
不会更改this
函数,因此就像调用f(x)
。 通常这是一件好事,但是如果您写:
var obj = { x: 5, f: () => this.
x }
var obj = { x: 5, f: () => this.
X }
You should realize that obj.f()
does not return obj.x
.
你应该认识到obj.f()
不返回obj.x
。
经验教训 (Lessons)
To avoid these holes, you need to:
为避免这些漏洞,您需要:
Not treat an object as a “baser” type (e.g. don’t treat
D
as anO
) unless you are sure that the baser type won’t be modified in a way that could violate the type system.除非您确定不会以可能违反类型系统的方式修改基本类型,否则不要将对象视为“ baser”类型(例如,不要将
D
视为O
)。Not treat an array as a “baser” type (e.g. don’t treat
D[]
asO[]
, or[A,B]
as(A|B)[]
) unless you are sure that the baser type won’t be modified in a way that could violate the type system.不要将数组视为“ baser”类型(例如,不要将
D[]
视为O[]
或将[A,B]
视为(A|B)[]
),除非您确定基本类型不会以可能违反类型系统的方式进行修改。- Be careful not to leave any “holes” with undefined values in your arrays.注意不要在数组中留下任何带有未定义值的“空洞”。
- Be careful not to use out-of-bounds array indexes.注意不要使用越界数组索引。
Not override a base-class method with covariant parameters.
不使用协变参数覆盖基类方法。
Avoid treating a class
K
as though it were an interface, unless you are sure that no code will ever check the type withinstanceof
.避免将类
K
视为接口,除非您确定没有代码会使用instanceof
检查类型。Avoid using
.call(...)
, and be careful how you deal with references to functions.避免使用
.call(...)
,并要小心处理对函数的引用。
TypeScript actually had more holes in the past, which are now plugged.
TypeScript过去实际上有更多 漏洞 ,现在已被填补。
JSX (JSX)
React introduced the concept of JSX code. Or, maybe, Hyperscript introduced it and React copied the idea soon afterward. In any case, JSX looks like HTML/XML code. But you are not making DOM elements, you’re making plain-old JavaScript objects, which we call a “virtual DOM”. For example, <img src={imageUrl
}/> actually means React.createElement("img", { src: image
Url }) in a .jsx or .tsx file.
React引入了JSX代码的概念。 或者,也许Hyperscript引入了它,然后React很快就复制了这个想法。 无论如何,JSX 看起来像HTML / XML代码。 但是,您不是在创建DOM元素,而是在创建普通JavaScript对象,我们称之为“虚拟DOM”。 例如, <img src={imageUrl
} />实际上means React.createElement("img", { src: image
.jsx或.tsx文件中的means React.createElement("img", { src: image
Url})。
If JSX is a React thing, why am I talking about it in the TypeScript section? Because support for JSX is built into the TypeScript compiler. To get JSX support in any TypeScript file, you just have to change the file’s extension from .ts
to .tsx
.
如果JSX是React的东西,为什么在TypeScript部分讨论它呢? 因为TypeScript编译器内置了对JSX的支持。 要在任何TypeScript文件中获得JSX支持,只需将文件的扩展名从.ts
更改为.tsx
。
JSX can be used in the same places as normal expressions: you can pass JSX code to a function…
JSX可以在与正则表达式相同的地方使用:您可以将JSX代码传递给函数…
ReactDOM.render(<h1>I'm JSX code!</h1>, document.body);
you can store it in a variable…
您可以将其存储在变量中...
let variable = <h1>I'm JSX code!</h1>;
and you can return it from a function…
您可以从函数中返回它…
return <h1>I'm JSX code!</h1>;
Because <h1>I'm JSX code
!</h1> really just means React.createElement("h1", null, "I'm
JSX code!").
因为<h1>I'm JSX code
!</ h1>实际上just means React.createElement("h1", null, "I'm
JSX代码!”)。
It is important whether a JSX tag starts with a capital letters — it is translated to TypeScript (or JavaScript) differently if it does. For example:
JSX标记是否以大写字母开头非常重要-JSX标记以不同的方式转换为TypeScript(或JavaScript)。 例如:
<div class="foo
"/>means React.createElement('div', {"class":"
foo"}), but<div class="foo
” />means React.createElement('div', {"class":"
foo”}),但是<Div class="foo
"/>means React.createElement(Div, {"class":"
foo"}) (without quotes arou
nd Div).<Div class="foo
”/>means React.createElement(Div, {"class":"
foo”的})(没有引号一个rou
ND DIV)。
Tips for using JSX:
使用JSX的提示:
JSX is XML-like, so all tags must be closed: write
<b
r/>, no
t <br>.JSX类似于XML,因此必须关闭所有标签:写
<b
r />, no
写<br>。JSX only supports string attributes and JavaScript expressions. When writing numeric attributes in TypeScript, use
<input type="number" min={0} max={100
}/>, because m
ax=100 is a syntax error and max
="100" is a type error.JSX仅支持字符串属性和JavaScript表达式。 在TypeScript中编写数字属性时,请使用
<input type="number" min={0} max={100
} />,cause m
ax = 100是语法错误,r and max
=“ 100”是类型错误。In React/Preact, you can use an array of elements in any location where a list of children are expected. For example, instead of
return <p>Ann<br/>Bob
<br/>Cam</p>, you can write let x = [<br/>, 'Bob', &
lt;br/>]; return <p>Ann{x}Cam</p>. This has the same effect because React/Preact “flattens” arrays in the child list.在React / Preact中,您可以在需要子列表的任何位置使用元素数组。 例如,
t;/p>, you can write let x = [<br/>, 'Bob', &
lt; br />];而不是return <p>Ann<br/>Bob
<br/> Cam&lt;/p>, you can write let x = [<br/>, 'Bob', &
; 返回<p> Ann {x} Cam </ p>。 这具有相同的效果,因为子列表中的React / Preact“拉平”数组。In React, the
class
attribute is not supported for some reason. UseclassName
instead.在React中,出于某些原因不支持
class
属性。 请改用className
。JSX itself does not support optional property or children. For example, suppose you write
<Foo prop={
x}> but you want to omit th
e prop whenx is und
efined. Well, JSX itself doesn’t support anything like that. However, most components treat an und
efined property the same as a missing property, so it usually works anyway. JSX doesn’t support optional children either, but you can get the same effect with an empty array: because arrays are “collapsed” by React/Preact, <Foo>
{ [] }</Foo> has the same eff
ect as <Foo></F
oo>. <Foo>{undefined}</Foo> does not have this effect(you end
up with a single child equal to undefined.)JSX本身不支持可选属性或子级。 For example, suppose you write
<Foo prop={
x}> but you want to omit th
e prop whenx is und
efined. Well, JSX itself doesn't support anything like that. However, most components treat an und
efined property the same as a missing property, so it usually works anyway. JSX doesn't support optional children either, but you can get the same effect with an empty array: because arrays are “collapsed” by React/Preact, <Foo>
{ [] }</Foo> has the same eff
ect as <Foo></F
oo>. <Foo>{undefined}</Foo> does not have this effect(you end
up with a single child equal to undefined.)If you have an object like
obj = {a:1, b:2}
and you would like to use all the properties of the object as properties of a Component, you can write<Component {...obj
}/>. The dots are always required; <Componen
t {obj}/> is not allowed.If you have an object like
obj = {a:1, b:2}
and you would like to use all the properties of the object as properties of a Component, you can write<Component {...obj
}/>. The dots are always required; <Componen
t {obj}/> is not allowed.
At the top of the file, the @jsx
pragma can control the “factory” function that is called to translate JSX elements. For example if you use /** @jsx h */
then <b>th
is</b> translates to h('b', n
ull, "this") instead of React.createElement('b', n
ull, "this"). Some Preact apps use th
is pragma (h is the preact function to create elements), but you won’t need to use it in this tutorial (c
reateElement is a s
ynonym for h). Also, in “tsconfig.json” you can get the same effect with "jsxF
actory": "h" in the com
pilerOptions.
At the top of the file, the @jsx
pragma can control the “factory” function that is called to translate JSX elements. For example if you use /** @jsx h */
then <b>th
is</b> tr anslates to h('b', n
ull, "this") instead of React.createElement('b', n
ull, "this"). Some Preact apps use t h
is pragma (h is the preact function to create elements), but you won't need to use it in thi s tutorial (c
reateElement is a s
ynonym for h). Also, in “tsconfig.json” you can get the same effect with "jsxF
actory": "h" in the com
pilerOptions.
也可以看看 (See also)
TypeScript evolution explains the newest TypeScript features in more detail. You might also like to see Advanced Types in TypeScript’s manual.
TypeScript evolution explains the newest TypeScript features in more detail. You might also like to see Advanced Types in TypeScript's manual.
你走之前… (Before you go…)
If you liked this article, don’t forget to clap or tweet! And if you’d like to learn React, continue on to my next article.
If you liked this article, don't forget to clap or tweet! And if you'd like to learn React, continue on to my next article .
翻译自: https://www.freecodecamp.org/news/typescript-and-its-types-f509d799947d/
快速而深入地了解TypeScript及其类型相关推荐
- TypeScript高级类型:联合类型、交叉类型和类型别名
目录 引言 联合类型 交叉类型 类型别名 注意 结论 引言 TypeScript 是一门强类型语言,其高级类型功能使得开发者能够更加灵活地定义类型并对其进行操作,以便于更好地编写可维护和可扩展的代码. ...
- 简单探讨TypeScript 枚举类型
这篇文章主要介绍了TypeScript 枚举类型,TypeScript 在 ES 原有类型基础上加入枚举类型,使得在 TypeScript 中也可以给一组数值赋予名字,这样对开发者比较友好,可以理解枚 ...
- TypeScript 基础类型 1
TypeScript 基础类型 自本节起,我们将开始接触 TypeScript 的类型系统,这也是 TypeScript 最为核心的部分. 本节介绍 TypeScript 中一些基础类型,有些特殊类型 ...
- 如何快速查看Linux系统上的Shell类型
要快速查看Linux系统上的Shell类型,可以参考本经验以下内容. 一.查看当前系统中所有可登录shell的类型 1 要查看当前系统中所有可登录shell的类型,在/etc/shells配置文件中记 ...
- 微信公众号(一)每日推送详细教程(含实时定位,天气预报,每日英语,纪念日等,可快速自定义消息模板并指定订阅者类型发送)
微信公众号(一)每日推送,天气推送 (含实时定位,天气预报,每日英语,纪念日等,可快速自定义消息模板并指定订阅者类型发送),另有小白网页版配置 版本介绍 1. 相关API接口申请 1.1 微信 1.2 ...
- TypeScript 基础类型+函数+接口+类
1.简介: TypeScript 是 JavaScript 的一个超集.由微软开发的自由和开源的编程语言.设计目标是开发大型应用.是一种面向对象的编程语言,遵循强类型 javascript与types ...
- PART16 TypeScript高级类型
文章目录 TypeScript高级类型 class类 构造函数 实例方法 类的继承 类成员的可见性 类型兼容性 接口之间的兼容性 函数之间的兼容性 参数个数 参数类型 返回值类型 交叉类型 泛型 泛型 ...
- typescript索引类型_TypeScript类型声明书写详解
本文总结一下TypeScript类型声明的书写,很多时候写TypeScript不是问题,写类型就特别纠结,我总结下,我在使用TypeScript中遇到的问题.如果你遇到类型声明不会写的时候,多看看lo ...
- Typescript 基本类型
基础知识脑补下 在 JavaScript 的类型分为两种: 原始数据类型(Primitive data types) 对象类型(Object types) 其中,原始数据类型包括:布尔值.数字.字符串 ...
最新文章
- 只知道TF和PyTorch还不够,快来看看怎么从PyTorch转向自动微分神器JAX
- matplotlib绘图库入门
- 成幻Online Judge 1.00 Beta 正式发布 2007.6.22
- mysql 唯一编号_Mysql表中唯一编号的分配机制
- WindowsXP 下的pix模拟器出炉了!!!
- 静态代理和JDK动态代理
- MonoRail学习笔记十一:页面控件的填充和验证
- 【转载】asp.net中弹出确认窗口(confirm),实现删除确认的功能
- 函数式编程4-高阶函数
- error超频 whea win10_解决WHEA_UNCORRECTABLE_ERROR蓝屏
- (转)OpenLayers3基础教程——OL3基本概念
- 冈萨雷斯--数字图像处理(MATLAB版)----书籍相关网站
- css3中定义required,focus,valid和invalid样式
- 将公司的主要项目从eclipse迁移到android studio for mac环境(1)
- 微信商户现金红包api php
- zabbix的Discovery功能
- 怎么检查计算机网络是连接,电脑怎么查看网络连接
- 插入排序和迭代归并排序以及复杂度分析
- 定时监控服务端口是否正常 发送邮件
- ubuntu更新过程中出现错误:校验数字签名时出错。此仓库未被更新,下列签名无效