Typescript学习笔记(二)

泛型

在像C#和Java这样的语言中,可使用泛型来建立可重用的组件,一个组件能够支持多种类型的数据。 这样用户就能够以本身的数据类型来使用组件。算法

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

function identity<T>(arg: T): T {
    return arg;
}
复制代码

咱们给identity添加了类型变量T。 T帮助咱们捕获用户传入的类型(好比:number),以后咱们就可使用这个类型。 以后咱们再次使用了 T当作返回值类型。如今咱们能够知道参数类型与返回值类型是相同的了。 这容许咱们跟踪函数里使用的类型的信息。bash

咱们把这个版本的identity函数叫作泛型,由于它能够适用于多个类型。传入数值类型并返回数值类型。app

声明泛型方法有如下两种方式:

function generics_func1<T>(arg: T): T {
    return arg;
}
// 或者
let generics_func2: <T>(arg: T) => T = function (arg) {
    return arg;
}
复制代码

咱们定义了泛型函数后,能够用两种方法使用。ide

第一种是,传入全部的参数,包含类型参数:函数

let output = identity<string>("myString");  // type of output will be 'string'
复制代码

第二种方法更广泛。利用了类型推论 -- 能够省略类型参数,由于编译器会根据传入参数来自动识别对应的类型。ui

let output = identity("myString");  // type of output will be 'string'
复制代码

在方法一的方法体里,打印了arg参数的length属性。由于any能够代替任意类型,因此该方法在传入参数不是数组或者带有length属性对象时,会抛出异常。而方法二定义了参数类型是Array的泛型类型,确定会有length属性,因此不会抛出异常。可是若是不加array或[]也没有length属性。this

从下面这个例子能够看出,泛型类型相比较any类型来讲,在某些状况下会带有类型自己的一些信息,而any类型则没有。spa

// 方法一:带有any参数的方法
function any_func(arg: any): any {
    console.log(arg.length);
    return arg;
}

// 方法二:Array泛型方法
function array_func<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);
    return arg;
}
复制代码

泛型类型

泛型接口调试

interface Generics_interface<T> {
    (arg: T): T;
}

function func_demo<T>(arg: T): T {
    return arg;
}

let func1: Generics_interface<number> = func_demo;
func1(123);     // 正确类型的实际参数
func1('123');   // 错误类型的实际参数(由于已经定义为number了)
复制代码

经过在接口上声明泛型,声明变量时明确指定泛型的具体类型,则赋值的方法将自动带上具体的类型约束。

泛型类型继承

interface LengthInterface {
    length: number;
}

function func_demo<T extends LengthInterface>(arg: T): T {
    console.log(arg.length);
    return arg;
}

func_demo({ a: 1, length: 2 });     // 含有length属性的对象
func_demo([1, 2]);                  // 数组类型
复制代码

上面的例子里,泛型类型继承自一个拥有length属性成员的接口,泛型类型将自动加上length属性的约束。调用时只有符合条件的对象才能正确赋值。

function copy<T extends U, U>(source: U, target: T): T {
    for (let prop in source) {
        target[prop] = source[prop];
    }

    return target;
}

copy({ a: 1, b: 2 }, { a: 2, b: 3, c: 4 });         // 正确的实际参数
copy({ a: 1, b: 2 }, { q: 2, c: 4 });               // 错误的实际参数
复制代码

在上面的例子里,一个泛型类型继承自另一个泛型类型。在方法调用时,就必须确保继承类型对应的参数对象属性彻底包含被继承类型对应的参数对象。

泛型类

class Generics_Demo<T>{
    value: T;
    show(): T {
        return this.value;
    }
}

let gene_demo1 = new Generics_Demo<number>();
gene_demo1.value = 1;
console.log(gene_demo1.show());                                     // 调用方法

gene_demo1.show = function () { return gene_demo1.value + 1; }      // 赋值新方法,返回值类型必须是number
console.log(gene_demo1.show());
复制代码

经过指定明确类型的泛型类的实例,对属性赋值时,必须知足实际类型的约束。

枚举

数字枚举

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}
复制代码

如上,咱们定义了一个数字枚举, Up使用初始化为 1。 其他的成员会从 1开始自动增加。换句话说, Direction.Up的值为 1, Down为 2, Left为 3, Right为 4若是不定义初始值的话就是从0开始。

使用枚举很简单:经过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型:

enum Response {
    No = 0,
    Yes = 1,
}

function respond(recipient: string, message: Response): void {
    // ...
}

respond("Princess Caroline", Response.Yes)
复制代码

下面的状况是不被容许的:

enum E {
    A = getSomeValue(),
    B, // error! 'A' is not constant-initialized, so 'B' needs an initializer
}
复制代码

字符串枚举

在一个字符串枚举里,每一个成员都必须用字符串字面量,或另一个字符串枚举成员进行初始化。

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}
复制代码

因为字符串枚举没有自增加的行为,字符串枚举能够很好的序列化。 换句话说,若是你正在调试而且必需要读一个数字枚举的运行时的值,这个值一般是很难读的 - 它并不能表达有用的信息,字符串枚举容许你提供一个运行时有意义的而且可读的值,独立于枚举成员的名字。

异构枚举

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}
复制代码

从技术的角度来讲,枚举能够混合字符串和数字成员,可是咱们不建议这样作

计算的和常量成员

当知足以下条件时,枚举成员被看成是常量:

一、它是枚举的第一个成员且没有初始化器,这种状况下它被赋予值 0:

// E.X is constant:
enum E { X }
复制代码

二、它不带有初始化器且它以前的枚举成员是一个 数字常量。 这种状况下,当前枚举成员的值为它上一个枚举成员的值加1。

// All enum members in 'E1' and 'E2' are constant.

enum E1 { X, Y, Z }

enum E2 {
    A = 1, B, C
}
复制代码

三、枚举成员使用 常量枚举表达式初始化。常数枚举表达式是TypeScript表达式的子集,它能够在编译阶段求值。当一个表达式知足下面条件之一时,它就是一个常量枚举表达式:

一个枚举表达式字面量(主要是字符串字面量或数字字面量)

一个对以前定义的常量枚举成员的引用(能够是在不一样的枚举类型中定义的)

带括号的常量枚举表达式

一元运算符 +, -, ~其中之一应用在了常量枚举表达式

常量枚举表达式作为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操做对象。 若常数枚举表达式求值后为 NaN或Infinity,则会在编译阶段报错。

全部其它状况的枚举成员被看成是须要计算得出的值。

enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}
复制代码

类型推论

最佳通用类型

当须要从几个表达式中推断类型时候,例如

let x = [0, 1, null];
复制代码

为了推断x的类型,咱们必须考虑全部元素的类型。 这里有两种选择: number和null。 计算通用类型算法会考虑全部的候选类型,并给出一个兼容全部候选类型的类型。

模块

模块的导入和导出

模块在其自身的做用域里执行,而不是在全局做用域里;

这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用export之一导出它们。

相反,若是想使用其它模块导出的变量,函数,类,接口等的时候,你必需要导入它们,可使用import之一。

模块是自声明的。在TypeScript里,两个模块之间的关系是经过在文件级别上使用import和export创建的。下面是一个基本例子:

animal.ts

1 export class Animal {
2     name: string;
3     show(): string {
4         return this.name;
5     }
6 }
复制代码

app.ts

1 import {Animal} from './animal';
2 let dog = new Animal();
3 dog.name = '狗狗';
4 dog.show();
复制代码

上面的例子里,在animal.ts里声明了一个类Animal,经过export导出。在app.ts里,指定相对文件路径,经过import导入,就可使用Animal类。

导入和导出的重命名

导入和导出时,经过as关键字对模块进行重命名。 animal.ts

class Animal {
    name: string;
    show(): string {
        return this.name;
    }
}

export {Animal as ANI};
复制代码

app.ts

import {ANI as Animal} from './animal';
let dog = new Animal();
dog.name = '狗狗';
dog.show();
复制代码

导入和导出多个对象

MyLargeModule.ts

export class Dog { ... }
export class Cat { ... }
export class Tree { ... }
export class Flower { ... }
复制代码

Consumer.ts

import * as myLargeModule from "./MyLargeModule.ts";
let x = new myLargeModule.Dog();
复制代码
相关文章
相关标签/搜索