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-nodets-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最初确实采用了漏洞:任何变量都可以为nullundefined

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,您可能想知道nullundefined是什么,或者为什么它们是两个不同的东西 。

Well, I promised to tell you about TypeScript and null/undefined are JavaScript things. Ha!

好吧,我答应告诉您有关TypeScriptnull / undefinedJavaScript的事情。 哈!

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]也不是字符串,也不是数字。 ynumber[] | 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类允许变量声明,例如本示例中的widthheight

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指的是具有widthheight属性(可读数字)的任何类。 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实现了IBoxIArea而无需这么说。

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,Val> interface that represents a dictionary of key-value pairs. The new Map class built into ES6 also conforms to this interface, so you can put a Map into an IMap variable. So, for example, you can write a function with an 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。 例如,此boxIBoxIArea ,因此它具有两个接口的所有属性:

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:

在其他一些编程语言中,每种类型都有一个名称,例如stringdoubleComponent 。 在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 。 因此book1book2具有相同的类型,您可以将其中一个分配给另一个,也可以分配给另一本书。

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值分配给book1book2 。 但是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编写了不同的模块AB 它们都使用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有一个! 用于避免某些错误消息的运算符。 它的意思是“看,编译器,我知道您认为此变量可以为nullundefined ,但我向您保证不是。 因此,如果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的基于流的键入系统支持typeofinstanceof运算符以及普通的比较运算符。 如果从可能具有几种类型的变量开始,则可以使用以下任何一种运算符来缩小类型的范围:

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 Stringfalse"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" 。 基本类型为numberbooleanstringsymbolundefinednull 。 所有不是原始的东西都是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 an unshift 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 as constructor promotes thing to Boxed status, so even if thing 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) => number:

在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) =&gt; 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) =&gt; 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。 但是获取yearday的代码都是错误的。 由于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 Dateis 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("hello!") 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&lt;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 parameters, 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设置为nullundefined. 幸运的是,使用"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 。 运作方式如下:

  1. keyof X turns the properties of X into a union type of the names of the properties. For example, given the Book interface from earlier, keyof Book means "title" | "author" | "age". Likewise keyof TimeFormatOptions is any of the property names in TimeFormatOptions.

    keyof X打开的属性X成联合类型属性的名称。 例如,给定较早版本的Book界面, keyof Book表示"title" | "author" | "age" "title" | "author" | "age" "title" | "author" | "age" 。 同样keyof TimeFormatOptions是任何在属性名的TimeFormatOptions

  2. The “extends” constraint, X extends Y, means that “X must be Y, or a subtype of Y”. For example X extends Object means that X must be some kind of Object, which means it can be an array or a Date or even a function, all of which are considered to be Objects, but it can’t be a string or a number or a boolean. Similarly X extends Point means that X is Point or a more specific type than Point, such as Point3D.

    “扩展”约束X extends Y ,表示“ X必须是Y或Y的子类型”。 例如X extends Object意味着X必须是某种Object ,这意味着它可以是数组或Date甚至是一个函数,所有这些都被视为Object,但不能是stringnumberboolean 。 类似地, X extends Point意味着XPoint或比Point 更特定的类型,例如Point3D

  3. What would B extends keyof Book mean? It would mean that B 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 type keyof Book. However B cannot be string, 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”。

  4. Similarly, K is constrained to have a subtype of keyof TimeFormatOptions, such as "use24hourTime".

    同样, K被约束为具有keyof TimeFormatOptions的子类型,例如"use24hourTime"

  5. The type X[Y] means “the type of the Y property of X, where Y is a number or string literal”. For example, the type Book["author"] is string | 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模式下,您不能在数字数组中放置nullundefined

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 an O) 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[] as O[], 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 with instanceof.

    避免将类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: imageUrl }) 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 around 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 <br/>, not <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 max=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&lt;/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&l t;/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. Use className 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 the prop when x is undefined. Well, JSX itself doesn’t support anything like that. However, most components treat an undefined 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 effect as <Foo></Foo>. <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 omi t th e prop when x is und efined. Well, JSX itself doesn't support anything like that. However, most components tre at 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/Pr eact, <Foo> { [] }</Foo> has t he same eff ec t 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; <Component {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 requ ired; <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>this</b> translates to h('b', null, "this") instead of React.createElement('b', null, "this"). Some Preact apps use this pragma (h is the preact function to create elements), but you won’t need to use it in this tutorial (createElement is a synonym for h). Also, in “tsconfig.json” you can get the same effect with "jsxFactory": "h" in the compilerOptions.

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及其类型相关推荐

  1. TypeScript高级类型:联合类型、交叉类型和类型别名

    目录 引言 联合类型 交叉类型 类型别名 注意 结论 引言 TypeScript 是一门强类型语言,其高级类型功能使得开发者能够更加灵活地定义类型并对其进行操作,以便于更好地编写可维护和可扩展的代码. ...

  2. 简单探讨TypeScript 枚举类型

    这篇文章主要介绍了TypeScript 枚举类型,TypeScript 在 ES 原有类型基础上加入枚举类型,使得在 TypeScript 中也可以给一组数值赋予名字,这样对开发者比较友好,可以理解枚 ...

  3. TypeScript 基础类型 1

    TypeScript 基础类型 自本节起,我们将开始接触 TypeScript 的类型系统,这也是 TypeScript 最为核心的部分. 本节介绍 TypeScript 中一些基础类型,有些特殊类型 ...

  4. 如何快速查看Linux系统上的Shell类型

    要快速查看Linux系统上的Shell类型,可以参考本经验以下内容. 一.查看当前系统中所有可登录shell的类型 1 要查看当前系统中所有可登录shell的类型,在/etc/shells配置文件中记 ...

  5. 微信公众号(一)每日推送详细教程(含实时定位,天气预报,每日英语,纪念日等,可快速自定义消息模板并指定订阅者类型发送)

    微信公众号(一)每日推送,天气推送 (含实时定位,天气预报,每日英语,纪念日等,可快速自定义消息模板并指定订阅者类型发送),另有小白网页版配置 版本介绍 1. 相关API接口申请 1.1 微信 1.2 ...

  6. TypeScript 基础类型+函数+接口+类

    1.简介: TypeScript 是 JavaScript 的一个超集.由微软开发的自由和开源的编程语言.设计目标是开发大型应用.是一种面向对象的编程语言,遵循强类型 javascript与types ...

  7. PART16 TypeScript高级类型

    文章目录 TypeScript高级类型 class类 构造函数 实例方法 类的继承 类成员的可见性 类型兼容性 接口之间的兼容性 函数之间的兼容性 参数个数 参数类型 返回值类型 交叉类型 泛型 泛型 ...

  8. typescript索引类型_TypeScript类型声明书写详解

    本文总结一下TypeScript类型声明的书写,很多时候写TypeScript不是问题,写类型就特别纠结,我总结下,我在使用TypeScript中遇到的问题.如果你遇到类型声明不会写的时候,多看看lo ...

  9. Typescript 基本类型

    基础知识脑补下 在 JavaScript 的类型分为两种: 原始数据类型(Primitive data types) 对象类型(Object types) 其中,原始数据类型包括:布尔值.数字.字符串 ...

最新文章

  1. 只知道TF和PyTorch还不够,快来看看怎么从PyTorch转向自动微分神器JAX
  2. matplotlib绘图库入门
  3. 成幻Online Judge 1.00 Beta 正式发布 2007.6.22
  4. mysql 唯一编号_Mysql表中唯一编号的分配机制
  5. WindowsXP 下的pix模拟器出炉了!!!
  6. 静态代理和JDK动态代理
  7. MonoRail学习笔记十一:页面控件的填充和验证
  8. 【转载】asp.net中弹出确认窗口(confirm),实现删除确认的功能
  9. 函数式编程4-高阶函数
  10. error超频 whea win10_解决WHEA_UNCORRECTABLE_ERROR蓝屏
  11. (转)OpenLayers3基础教程——OL3基本概念
  12. 冈萨雷斯--数字图像处理(MATLAB版)----书籍相关网站
  13. css3中定义required,focus,valid和invalid样式
  14. 将公司的主要项目从eclipse迁移到android studio for mac环境(1)
  15. 微信商户现金红包api php
  16. zabbix的Discovery功能
  17. 怎么检查计算机网络是连接,电脑怎么查看网络连接
  18. 插入排序和迭代归并排序以及复杂度分析
  19. 定时监控服务端口是否正常 发送邮件
  20. ubuntu更新过程中出现错误:校验数字签名时出错。此仓库未被更新,下列签名无效

热门文章

  1. 如何制定GRE作文提纲
  2. 这短短的理念关于住宅屋面构造
  3. 汇川伺服RLS和FLS报警处理
  4. 【车载开发】Android车载操作系统来了,前景非常乐观
  5. 会打麻将的人工智能来了
  6. 今晚QQ上线N次,次次目的不同
  7. 第5章 运算符和表达式
  8. vex夹球机器人_科技小达人玩转“机器人” ——访VEX机器人世界锦标赛虹口代表团...
  9. CSS 过渡(CSS3)
  10. qq for linux安装教程,Linux for QQ 安装