20160114

最后一天看 TypeScript 教程

英文版:http://www.typescriptlang.org/Handbook
中文版: https://www.gitbook.com/book/zhongsp/typescript-handbook/details
TypeScript 转 JavaScript:http://www.typescriptlang.org/Playground

每天都会发现很多有意思的写法,对新东西充满好奇,然后做出东西来~

Generics(泛型)

A major part of software engineering is building components that not only have well-defined and consistent APIs, but are also reusable. Components that are capable of working on the data of today as well as the data of tomorrow will give you the most flexible capabilities for building up large software systems.
In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is ‘generics’, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

  • Hello World of Generics
  • To start off, let’s do the “hello world” of generics: the identity function. The identity function is a function that will return back whatever is passed in. You can think of this in a similar way to the ‘echo’ command.
    创建一个使用泛型的 helloworld 返回任何传入它的参数,可以和 echo 相似。
    不用泛型:
1.// 参数和返回值是 number 类型
2.function (arg: number): number{
3. return arg;
4.}
5.// 参数和返回值是 any 类型
6.function (arg: any): any{
7. return arg;
8.}
9.// 用 any 已经能接收到各种类型的参数,但是不能保证传入类型和返回类型相同
10.// 如果传入一个数字,任何类型的值都有可能被返回。

因此,我们需要一种方法使用返回值的类型与传入参数的类型是相同的。 这里,我们使用了类型变量,它是一种特殊的变量,只用于表示类型而不是值。

1.function identity<T>(arg: T):T{
2. return arg;
3.}

这样可以保证传入的类型的返回的类型相同。
用的时候可以。

1.var output = identity<string>("myString");
2.// 也可以不指定泛型,可以根据传入的参数推断类型
3.var output = identity("myString");
4.// 注意我们并没用<>明确的指定类型,编译器看到了myString,把T设置为此类型。 类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。
  • 使用泛型变量
    使用泛型创建像identity这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当做是任意或所有类型。
1.function loggingIdentity<T>(arg: T[]): T[]{
2. console.log(arg.length); // 如果在前面没有写参数为数组类型的话,不会有 length 属性
3. return arg;
4.}
5.// 也可以这么写
6.function loggingIdentity<T>(arg: Array<T>): Array<T>{
7. console.log(arg.length);
8. return arg;
9.}
  • 泛型类型
1.function identity<T>(arg: T):T{
2. return arg;
3.}
4.var myIdentity : <T>(arg: T) => T = identity;
5.// 也可以使用不同的名字
6.var myIdentity : <U>(arg: U) => U = identity;
7.// 还可以使用带有调用签名的对象字面量来定义泛型函数
8.var myIdentity : {<T>(arg: T) => T} = identity;
9.// 把对象字面量拿出来定义一个接口
10.interface GenericIdentityFn{
11. <T>(arg: T) => T;
12.}
13.function identity<T>(arg: T): T{
14. return arg;
15.}
16.var myIdentity : GenericdentityFn = identity;
17.// 可以把泛型参数写在外面
18.interface GenericIdentityFn<T>{
19. (arg: T): T;
20.}
21.function identity<T>(arg: T):T{
22. return arg;
23.}
24.var myIdentity: GenericIdentityFn<number> = identity;

对于描述哪部分类型属于泛型部分来说,理解何时把参数放在调用签名里和何时放在接口上是很有帮助的。

  • 泛型类
    泛型类看上去与泛型接口差不多。 泛型类使用(<>)括起泛型类型,跟在类名后面。
1.class GenericNumber<T>{
2. zeroValue: T;
3. add: (x: T,y: T) => T;
4.}
5.var myGenericNumber = new GenericNumber<number>();
6.myGenericNumber.zeroValue = 0;
7.myGenericNumber.add = function(x, y){ return x + y; }

与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型

  • Generic Constraints(泛型约束)
    If you remember from an earlier example, you may sometimes want to write a generic function that works on a set of types where you have some knowledge about what capabilities that set of types will have. In our ‘loggingIdentity’ example, we wanted to be able access the “.length” property of ‘arg’, but the compiler could not prove that every type had a “.length” property, so it warns us that we can’t make this assumption.
    希望处理带有 length 属性的所有类型。用泛型约束。
1.interface Lengthwise{
2. length: number;
3.}
4.function loggingIdentity<T extends Lengthwise>(args: T): T{
5. console.log(arg.length); // 必须有一个 length 属性才会被选取
6. return arg;
7.}
8.loggingIdentity({length: 10,value: 3}); // Thats all right
  • Using Type Parameters in Generic Constraints(在泛型约束中使用类型参数)
    In some cases, it may be useful to declare a type parameter that is constrained by another type parameter. For example,
    有时候,我们需要使用类型参数去约束另一个类型参数。比如,
1.function find<T>(n: T, s: Findable<T>) {
2. // ...
3.}
4.find(giraffe, myAnimals);
5.// 参数 s 的类型不能定义在泛型的 <> 里。
  • Using Class Types in Generics(在泛型里使用类类型)(待完善)
    When creating factories in TypeScript using generics, it is necessary to refer to class types by their constructor functions. For example,
    在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,
1.function create<T>(c: {new(): T; }): T { 
2. return new c();
3.}

一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。

1.class BeeKeeper {
2. hasMask: boolean;
3.}
4.class ZooKeeper {
5. nametag: string;
6.}
7.class Animal {
8. numLegs: number;
9.}
10.class Bee extends Animal {
11. keeper: BeeKeeper;
12.}
13.class Lion extends Animal {
14. keeper: ZooKeeper;
15.}
16.function findKeeper<A extends Animal, K> (a: {new(): A;
17. prototype: {keeper: K}}): K {
18. return a.prototype.keeper;
19.}
20.findKeeper(Lion).nametag; // typechecks!

Common Errors

The list below captures some of the commonly confusing error messages that you may encounter when using the TypeScript language and Compiler
下面列出了一些在使用TypeScript语言和编译器过程中常见的容易让人感到困惑的错误信息。

  1. “tsc.exe” exited with error code 1
    Fixes:
    check file-encoding is UTF-8 (检查文件编码,确保为UTF-8 )- https://typescript.codeplex.com/workitem/1587
  2. external module XYZ cannot be resolved
    Fixes:
    check if module path is case-sensitive (检查模块路径是否大小写敏感)- https://typescript.codeplex.com/workitem/2134

Mixins(待完善)

Along with traditional OO hierarchies, another popular way of building up classes from reusable components is to build them by combining simpler partial classes. You may be familiar with the idea of mixins or traits for languages like Scala, and the pattern has also reached some popularity in the JavaScript community.
除了传统的面向对象继承方式,还流行一种通过可重用组件创建类的方式,就是联合另一个简单类的代码。 你可能在Scala等语言里对mixins及其特性已经很熟悉了,但它在JavaScript中也是很流行的。

  • Mixin sample
    In the code below, we show how you can model mixins in TypeScript. After the code, we’ll break down how it works.
1.// Disposable Mixin
2.class Disposable {
3. isDisposed: boolean;
4. dispose() {
5. this.isDisposed = true;
6. }
7.
8.}
9.
10.// Activatable Mixin
11.class Activatable {
12. isActive: boolean;
13. activate() {
14. this.isActive = true;
15. }
16. deactivate() {
17. this.isActive = false;
18. }
19.}
20.
21.class SmartObject implements Disposable, Activatable {
22. constructor() {
23. setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
24. }
25.
26. interact() {
27. this.activate();
28. }
29.
30. // Disposable
31. isDisposed: boolean = false;
32. dispose: () => void;
33. // Activatable
34. isActive: boolean = false;
35. activate: () => void;
36. deactivate: () => void;
37.}
38.applyMixins(SmartObject, [Disposable, Activatable])
39.
40.var smartObj = new SmartObject();
41.setTimeout(() => smartObj.interact(), 1000);
42.
43.////////////////////////////////////////
44.// In your runtime library somewhere
45.////////////////////////////////////////
46.
47.function applyMixins(derivedCtor: any, baseCtors: any[]) {
48. baseCtors.forEach(baseCtor => {
49. Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
50. derivedCtor.prototype[name] = baseCtor.prototype[name];
51. })
52. });
53.}

理解这个例子
代码里首先定义了两个类,它们将做为mixins。 可以看到每个类都只定义了一个特定的行为或功能。 稍后我们使用它们来创建一个新类,同时具有这两种功能。

1.// Disposable Mixin
2.class Disposable {
3. isDisposed: boolean;
4. dispose() {
5. this.isDisposed = true;
6. }
7.
8.}
9.
10.// Activatable Mixin
11.class Activatable {
12. isActive: boolean;
13. activate() {
14. this.isActive = true;
15. }
16. deactivate() {
17. this.isActive = false;
18. }
19.}

Finally, we mix our mixins into the class, creating the full implementation.
下面创建一个类,结合了这两个mixins。 下面来看一下具体是怎么操作的:

1.class SmartObject implements Disposable, Activatable {

首先应该注意到的是,没使用extends而是使用implements。 把类当成了接口,仅使用Disposable和Activatable的类型而非其实现。 这意味着我们需要在类里面实现接口。 但是这是我们在用mixin时想避免的。+
我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性。

1.// Disposable
2.isDisposed: boolean = false;
3.dispose: () => void;
4.// Activatable
5.isActive: boolean = false;
6.activate: () => void;
7.deactivate: () => void;

最后,把mixins混入定义的类,完成全部实现部分。

1.applyMixins(SmartObject, [Disposable, Activatable]);

最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码。

1.function applyMixins(derivedCtor: any, baseCtors: any[]) {
2. baseCtors.forEach(baseCtor => {
3. Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
4. derivedCtor.prototype[name] = baseCtor.prototype[name];
5. })
6. });
7.}
  • 合并接口
    各接口内的顺序保持不变。
1.interface Box {
2. height: number;
3. width: number;
4.}
5.interface Box {
6. scale: number;
7.}
8.var box: Box = {height: 5, width: 6, scale: 10};

接口中的非函数成员名必须是唯一的
函数成员:同名的函数声明会被当做一个重载,每组接口中的顺序不变,只是靠后的接口声明会在靠前的接口之前。

  • 合并命名空间
    与接口相似,同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值,我们需要知道这两者都是怎么合并的。
    命名空间的合并,模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。
    值的合并,如果当前已经存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块里。
    除了这些合并外,你还需要了解非导出成员是如何处理的。 非导出成员仅在其原始存在于的命名空间(未合并的)之内可见。这就是说合并之后,从其它命名空间合并进来的成员无法访问非导出成员。(没有 export)

除了内部类的模式,你在JavaScript里,创建一个函数稍后扩展它增加一些属性也是很常见的。 Typescript使用声明合并来达到这个目的并保证类型安全。

1.function buildLabel(name: string): string {
2. return buildLabel.prefix + name + buildLabel.suffix;
3.}
4.namespace buildLabel {
5. export let suffix = "";
6. export let prefix = "Hello, ";
7.}
8.alert(buildLabel("Sam Smith"));

相似的,命名空间可以用来扩展枚举型:

1.enum Color {
2. red = 1,
3. green = 2,
4. blue = 4
5.}
6.
7.namespace Color {
8. export function mixColor(colorName: string) {
9. if (colorName == "yellow") {
10. return Color.red + Color.green;
11. }
12. else if (colorName == "white") {
13. return Color.red + Color.green + Color.blue;
14. }
15. else if (colorName == "magenta") {
16. return Color.red + Color.blue;
17. }
18. else if (colorName == "cyan") {
19. return Color.green + Color.blue;
20. }
21. }
22.}

Type Inference

In this section, we will cover type inference in TypeScript. Namely, we’ll discuss where and how types are inferred.
介绍类型在哪里被如何推断的。
当需要从几个表达式中推断类型时候,会使用这些表达式的类型来推断出一个最合适的通用类型。
由于最终的通用类型取自候选类型,有些时候候选类型共享相同的通用类型,但是却没有一个类型能做为所有候选类型的类型。例如:

1.var zoo = [new Rhino(), new Elephant(), new Snake()];

这里,我们想让zoo被推断为Animal[]类型,但是这个数组里没有对象是Animal类型的,因此不能推断出这个结果。 为了更正,当候选类型不能使用的时候我们需要明确的指出类型:

1.var zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

如果没有找到最佳通用类型的话,类型推论的结果是空对象类型,{}。 因为这个类型没有任何成员,所以访问其成员的时候会报错。

  • 上下文类型
    TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。比如:
    这个例子会得到一个类型错误,TypeScript类型检查器使用Window.onmousedown函数的类型来推断右边函数表达式的类型。 因此,就能推断出mouseEvent参数的类型了。(和 onmousedown 的参数类型一致?) 如果函数表达式不是在上下文类型的位置,mouseEvent参数的类型需要指定为any,这样也不会报错了。
1.window.onmousedown = function(mouseEvent: any) {
2. console.log(mouseEvent.buton); //<- Now, no error is given
3.}; // 这里不需要从 onmousedown 来推断 mouseEvent 的类型

上下文归类会在很多情况下使用到。 通常包含函数的参数,赋值表达式的右边,类型断言,对象成员和数组字面量和返回值语句。 上下文类型也会做为最佳通用类型的候选类型。比如:

1.function createZoo(): Animal[] {
2. return [new Rhino(), new Elephant(), new Snake()];
3.}

这个例子里,最佳通用类型有4个候选者:Animal,Rhino,Elephant和Snake。 当然,Animal会被做为最佳通用类型。

Type Compatibility(类型兼容性)

从函数赋值判断函数是否兼容
参数:少的赋给多的可以
返回值:多的赋给少的可以

  • 函数参数双向协变
    当比较函数参数类型时,只有当源函数参数能够赋值给目标函数或者反过来时才能赋值成功。 这是不稳定的,因为调用者可能传入了一个具有更精确类型信息的函数,但是调用这个传入的函数的时候却使用了不是那么精确的类型信息。 实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如,

类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内

私有成员会影响兼容性判断。 当类的实例用来检查兼容时,如果它包含一个私有成员,那么目标类型必须包含来自同一个类的这个私有成员。 这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

泛型

1.interface Empty<T> {
2.}
3.var x: Empty<number>;
4.var y: Empty<string>;
5.
6.x = y; // okay, y matches structure of x
7.// 对于没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较。

增加一个成员

1.interface NotEmpty<T> {
2. data: T;
3.}
4.var x: NotEmpty<number>;
5.var y: NotEmpty<string>;
6.
7.x = y; // error, x and y are not compatible
  • 高级主题
    子类型与赋值
    目前为止,我们使用了兼容性,它在语言规范里没有定义。 在TypeScript里,有两种类型的兼容性:子类型与赋值。 它们的不同点在于,赋值扩展了子类型兼容,允许给any赋值或从any取值和允许数字赋值给枚举类型或枚举类型赋值给数字。
    语言里的不同地方分别使用了它们之中的机制。 实际上,类型兼容性是由赋值兼容性来控制的甚至在implements和extends语句里。 更多信息,请参阅TypeScript语言规范

Writing .d.ts files(书写 .d.ts 文件)

When using an external JavaScript library, or new host API, you’ll need to use a declaration file (.d.ts) to describe the shape of that library. This guide covers a few high-level concepts specific to writing definition files, then proceeds with a number of examples that show how to transcribe various concepts to their matching definition file descriptions.
当使用外部JavaScript库或新的宿主API时,你需要一个声明文件(.d.ts)定义程序库的shape。 这个手册包含了写.d.ts文件的高级概念,并带有一些例子,告诉你怎么去写一个声明文件。
http://www.typescriptlang.org/Handbook#writing-dts-files