翻译:疯狂的技术宅html
阅读本文后,你应该可以理解如下代码的含义:git
interface Array<T> {
concat(...items: Array<T[] | T>): T[];
reduce<U>(
callback: (state: U, element: T, index: number, array: T[]) => U,
firstState?: U): U;
···
}
复制代码
若是你认为这段代码很是神秘 —— 那么我赞成你的意见。可是(我但愿证实)这些符号仍是相对容易学习的。一旦你能理解它们,就能立刻全面、精确的理解这种代码,从而无需再去阅读冗长的英文说明。es6
TypeScript 有一个在线运行环境。为了获得最全面的信息,你应该在 “Options” 菜单中打开全部选项开关。这至关于在 --strict
模式下运行TypeScript编译器。github
我在用 TypeScript 时老是喜欢打开 --strict
开关设置。没有它,程序可能会稍微好写一点,可是你也失去了静态类型检查的好处。目前此设置可以开启如下子设置:typescript
--noImplicitAny
:若是 TypeScript 没法推断类型,则必须指定它。这主要用于函数和方法的参数:使用此设置,你必须对它们进行注释。--noImplicitThis
:若是 this
的类型不清楚则会给出提示信息。--alwaysStrict
:尽量使用 JavaScript 的严格模式。--strictNullChecks
:null
不属于任何类型(除了它本身的类型,null
),若是它是可接受的值,则必须明确指定。--strictFunctionTypes
:对函数类型更加严格的检查。--strictPropertyInitialization
:若是属性的值不能是 undefined
,那么它必须在构造函数中进行初始化。更多信息:TypeScript 手册中的“编译器选项”一章。编程
在本文中,咱们把类型看做是一组值的集合。 JavaScript 语言(不是TypeScript!)有7种类型:数组
undefined
的集合。false
和 true
的集合。全部这些类型都是 dynamic:能够用在运行时。ecmascript
TypeScript 为 JavaScript 带来了额外的层:静态类型。这些仅在编译或类型检查源代码时存在。每一个存储位置(变量或属性)都有一个静态类型,用于预测其动态值。类型检查可确保这些预测可以实现。还有不少能够进行 静态 检查(不运行代码)的东西。例如,若是函数 f(x)
的参数 x
是静态类型 number
,则函数调用 f('abc')
是非法的,由于参数 'abc'
是错误的静态类型。编程语言
变量名后的冒号开始 类型注释:冒号后的类型签名用来描述变量能够接受的值。例如以代码告诉 TypeScript 变量 “x” 只能存储数字:
let x: number;
复制代码
你可能想知道用 undefined
去初始化 x
是否是违反了静态类型。 TypeScript 不会容许这种状况出现,由于在为它赋值以前不容许操做 x
。
即便在 TypeScript 中每一个存储位置都有静态类型,你也没必要老是明确的去指定它。 TypeScript 一般能够对它的类型进行推断。例如若是你写下这行代码:
let x = 123;
复制代码
而后 TypeScript 会推断出 x
的静态类型是 number
。
在类型注释的冒号后面出现的是所谓的类型表达式。这些范围从简单到复杂,并按以下方式建立。
基本类型是有效的类型表达式:
对应 JavaScript 动态类型的静态类型:
undefined
, null
boolean
, number
, string
symbol
object
注意:值 undefined
与类型 undefined
(取决于所在的位置)
TypeScript 的特定类型:
Array
(从技术上讲不是 JS 中的类型)any
(全部值的类型)请注意,“undefined
做为值“ 和 ”undefined
做为类型” 都写作 undefined
。根据你使用它的位置,被解释为值或类型。 null
也是如此。
你能够经过类型运算符对基本类型进行组合的方式来建立更多的类型表达式,这有点像使用运算符 union(∪
)和intersection(∩
)去合并集合。
下面介绍 TypeScript 提供的一些类型运算符。
数组在 JavaScript 中扮演如下两个角色(有时是二者的混合):
数组 arr
被用做列表有两种方法表示 ,其元素都是数字:
let arr: number[] = [];
let arr: Array<number> = [];
复制代码
一般若是存在赋值的话,TypeScript 就能够推断变量的类型。在这种状况下,实际上你必须帮它解决类型问题,由于在使用空数组时,它没法肯定元素的类型。
稍后咱们将回到尖括号表示法(Array<number>
)。
若是你想在数组中存储二维坐标点,那么就能够把这个数组看成元组去用。看上去是这个样子:
let point: [number, number] = [7, 5];
复制代码
在这种状况下,你不须要类型注释。
另一个例子是 Object.entries(obj)
的返回值:一个带有一个 [key,value] 对的数组,它用于描述 obj
的每一个属性。
> Object.entries({a:1, b:2})
[ [ 'a', 1 ], [ 'b', 2 ] ]
复制代码
Object.entries()
的返回值类型是:
Array<[string, any]>
复制代码
如下是函数类型的例子:
(num: number) => string
复制代码
这个类型是一个函数,它接受一个数字类型参数而且返回值为字符串。在类型注释中使用这种类型(String
在这里是个函数)的例子:
const func: (num: number) => string = String;
复制代码
一样,咱们通常不会在这里使用类型注释,由于 TypeScript 知道 String
的类型,所以能够推断出 func
的类型。
如下代码是一个更实际的例子:
function stringify123(callback: (num: number) => string) {
return callback(123);
}
复制代码
因为咱们使用了函数类型来描述 stringify123()
的参数 callback
,因此TypeScript 拒绝如下函数调用。
f(Number);
复制代码
但它接受如下函数调用:
f(String);
复制代码
对函数的全部参数进行注释是一个很好的作法。你还能够指定返回值类型(不过 TypeScript 很是擅长去推断它):
function stringify123(callback: (num: number) => string): string {
const num = 123;
return callback(num);
}
复制代码
void
void
是函数的特殊返回值类型:它告诉 TypeScript 函数老是返回 undefined
(显式或隐式):
function f1(): void { return undefined } // OK
function f2(): void { } // OK
function f3(): void { return 'abc' } // error
复制代码
标识符后面的问号表示该参数是可选的。例如:
function stringify123(callback?: (num: number) => string) {
const num = 123;
if (callback) {
return callback(num); // (A)
}
return String(num);
}
复制代码
在 --strict
模式下运行 TypeScript 时,若是事先检查时发现 callback
没有被省略,它只容许你在 A 行进行函数调用。
TypeScript支持 ES6 参数默认值:
function createPoint(x=0, y=0) {
return [x, y];
}
复制代码
默认值可使参数可选。一般能够省略类型注释,由于 TypeScript 能够推断类型。例如它能够推断出 x
和 y
都是 number
类型。
若是要添加类型注释,应该这样写:
function createPoint(x:number = 0, y:number = 0) {
return [x, y];
}
复制代码
你还能够用 ES6 rest operator 进行 TypeScript 参数定义。相应参数的类型必须是数组:
function joinNumbers(...nums: number[]): string {
return nums.join('-');
}
joinNumbers(1, 2, 3); // '1-2-3'
复制代码
在JavaScript中,有时候变量会是有几种类型之中的一种。要描述这些变量,可使用 union types。例如,在下面的代码中,x
是 null
类型或 number
类型:
let x = null;
x = 123;
复制代码
x
的类型能够描述为 null | number
:
let x: null|number = null;
x = 123;
复制代码
类型表达式 s | t
的结果是类型 s
和 t
在集合理论意义上的联合(正如咱们以前看到的那样,两个集合)。
下面让咱们重写函数 stringify123()
:此次咱们不但愿参数 callback
是可选的。应该老是调用它。若是调用者不想传入一个函数,则必须显式传递 null
。实现以下。
function stringify123(
callback: null | ((num: number) => string)) {
const num = 123;
if (callback) { // (A)
return callback(123); // (B)
}
return String(num);
}
复制代码
请注意,在行 B 进行函数调用以前,咱们必须再次检查 callback
是否真的是一个函数(行A)。若是没有检查,TypeScript 将会报告错误。
undefined|T
类型为 T
的可选参数和类型为 undefined|T
的参数很是类似。 (另外对于可选属性也是如此。)
主要区别在于你能够省略可选参数:
function f1(x?: number) { }
f1(); // OK
f1(undefined); // OK
f1(123); // OK
复制代码
But you can’t omit parameters of type 可是你不能省略 undefined|T
类型的参数:
function f2(x: undefined | number) { }
f2(); // error
f2(undefined); // OK
f2(123); // OK
复制代码
null
和 undefined
一般不包含在类型中在许多编程语言中,null
是全部类型的一部分。例如只要 Java 中的参数类型为 String
,就能够传递 null
而Java 不会报错。
相反,在TypeScript中,undefined
和 null
由单独的不相交类型处理。若是你想使它们生效,必需要有一个类型联合,如 undefined|string
和 null|string
。
与Arrays相似,对象在 JavaScript 中扮演两个角色(偶尔混合和/或更加动态):
咱们将在本文章中忽略 object-as-dictionaries。顺便说一句,不管如何,map 一般是比字典的更好选择。
接口描述 objects-as-records 。例如:
interface Point {
x: number;
y: number;
}
复制代码
TypeScript 类型系统的一大优点在于它的结构上,而不是在命名上。也就是说,接口 Point
可以匹配适当结构的全部对象:
function pointToString(p: Point) {
return `(${p.x}, ${p.y})`;
}
pointToString({x: 5, y: 7}); // '(5, 7)'
复制代码
相比之下,Java 的标称类型系统须要类来实现接口。
若是能够省略属性,则在其名称后面加上一个问号:
interface Person {
name: string;
company?: string;
}
复制代码
接口内还能够包含方法:
interface Point {
x: number;
y: number;
distance(other: Point): number;
}
复制代码
使用静态类型,能够有两个级别:
同理:
普通变量经过 const
,let
等引入。类型变量经过尖括号( <>
)引入。例如如下代码包含类型变量 T
,经过 <T>
引入。
interface Stack<T> {
push(x: T): void;
pop(): T;
}
复制代码
你能够看到类型参数 T
在 Stack
的主体内出现两次。所以,该接口能够直观地理解以下:
Stack
是一堆值,它们都具备给定的类型 T
。每当你提到 Stack
时,必须写 T
。接下来咱们会看到究竟该怎么用。.push()
接受类型为 T
的值。.pop()
返回类型为 T
的值。若是使用 Stack
,则必须为 T
指定一个类型。如下代码显示了一个虚拟栈,其惟一目的是匹配接口。
const dummyStack: Stack<number> = {
push(x: number) {},
pop() { return 123 },
};
复制代码
map 在 TypeScript 中的定义。例如:
const myMap: Map<boolean,string> = new Map([
[false, 'no'],
[true, 'yes'],
]);
复制代码
函数(和方法)也能够引入类型变量:
function id<T>(x: T): T {
return x;
}
复制代码
你能够按如下方式使用此功能。
id<number>(123);
复制代码
因为类型推断,还能够省略类型参数:
id(123);
复制代码
函数能够将其她的类型参数传给接口、类等:
function fillArray<T>(len: number, elem: T) {
return new Array<T>(len).fill(elem);
}
复制代码
类型变量 T
在这段代码中出现三次:
fillArray<T>
:引入类型变量elem:T
:使用类型变量,从参数中选择它。Array<T>
:将 T
传递给 Array
的构造函数。这意味着:咱们没必要显式指定Array<T>
的类型 T
—— 它是从参数 elem
中推断出来的:
const arr = fillArray(3, '*');
// Inferred type: string[]
复制代码
让咱们用前面学到的知识来理解最开始看到的那段代码:
interface Array<T> {
concat(...items: Array<T[] | T>): T[];
reduce<U>(
callback: (state: U, element: T, index: number, array: T[]) => U,
firstState?: U): U;
···
}
复制代码
这是一个Array的接口,其元素类型为 T
,每当使用这个接口时必须填写它:
.concat()
有零个或多个参数(经过 rest 运算符定义)。其中每个参数中都具备类型 T[]|T
。也就是说,它是一个 T
类型的数组或是一个 T
值。.reduce()
引入了本身的类型变量 U
。 U
表示如下实体都具备相同的类型(你不须要指定,它是自动推断的):
state
of callback()
(which is a function)state
是 callback()
的参数(这是一个函数)callback()
callback()
的返回.reduce()
的可选参数 firstState
.reduce()
.reduce()
的返回callback
还将得到一个 element
参数,其类型与 Array 元素具备相同的类型 T
,参数 index
是一个数字,参数 array
是 T
的值。