泛型
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(arg: T): T {
return arg;
}
复制代码
我们给identity添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。
我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型。传入数值类型并返回数值类型。
声明泛型方法有以下两种方式:
function generics_func1<T>(arg: T): T {
return arg;
}
// 或者
let generics_func2: <T>(arg: T) => T = function (arg) {
return arg;
}
复制代码
我们定义了泛型函数后,可以用两种方法使用。
第一种是,传入所有的参数,包含类型参数:
let output = identity<string>("myString"); // type of output will be 'string'
复制代码
第二种方法更普遍。利用了类型推论 -- 可以省略类型参数,因为编译器会根据传入参数来自动识别对应的类型。
let output = identity("myString"); // type of output will be 'string'
复制代码
在方法一的方法体里,打印了arg参数的length属性。因为any可以代替任意类型,所以该方法在传入参数不是数组或者带有length属性对象时,会抛出异常。而方法二定义了参数类型是Array的泛型类型,肯定会有length属性,所以不会抛出异常。但是如果不加array或[]也没有length属性。
从下面这个例子可以看出,泛型类型相比较any类型来说,在某些情况下会带有类型本身的一些信息,而any类型则没有。
// 方法一:带有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",
}
复制代码
从技术的角度来说,枚举可以混合字符串和数字成员,但是我们不建议这样做
计算的和常量成员
当满足如下条件时,枚举成员被当作是常量:
1、它是枚举的第一个成员且没有初始化器,这种情况下它被赋予值 0:
// E.X is constant:
enum E { X }
复制代码
2、它不带有初始化器且它之前的枚举成员是一个 数字常量。 这种情况下,当前枚举成员的值为它上一个枚举成员的值加1。
// All enum members in 'E1' and 'E2' are constant.
enum E1 { X, Y, Z }
enum E2 {
A = 1, B, C
}
复制代码
3、枚举成员使用 常量枚举表达式初始化。常数枚举表达式是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();
复制代码