系列文章
交叉类型(Intersection Types)
交叉类型是将多个类型合并为一个类型,包含这些类型的所有成员。比如A & B & C,则包含A,B,C这三种类型的成员。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor (public name: string) {}
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {}
}
const jim = extend(new Person('Jim'), new ConsoleLogger());
const n = jim.name;
jim.log();
联合类型(Union Types)
指定一个属性既可以传string又可以传number类型的值。
interface Style {
padding: number | string; // 既可以number又可以string
}
// any会导致传入的值不止限于string和number,可以是其他任意类型,因此不符合需求
interface Style {
padding: any;
}
高级使用
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
}
let pet = getSmallPet();
如何判断当前pet为Fish还是Bird?如何检查pet是否含有fly或swim方法?
// 错误的方式-每一个成员访问都会报错
if (pet.swim) {
pet.swim();
} else if (pet.fly) {
pet.fly();
}
// 推荐的方式,使用断言
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
} else {
(<Bird>pet).fly();
}
类型保护
TypeScript里的类型保护机制——类型保护就是一些表达式,他们会在运行时检查以确保在某个作用域里的类型。
如何定义一个类型保护?
只要简单的定义一个函数,他的返回值是一个类型谓词。
// 类型谓词:parameterName is Type的形式,如本例中的 pet is Fish
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
typeof类型保护
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
// 等同于
// 不必将 typeof x === "number"抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
instanceof类型保护
要求:
- 此构造函数的prototype属性的类型,如果他的类型不为any
- 构造签名所返回的类型的联合
字符串字面量类型
字符串字面量类型允许你指定字符串必须的固定值。
// 通过type设置类型别名
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
}
else if (easing === "ease-out") {
}
else if (easing === "ease-in-out") {
}
else {
// error! should not pass null or undefined.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
你只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误。
常用的有 HTMLImageElement、HTMLInputElement、Element等。
枚举成员类型
可辨识联合(Discriminated Unions)
你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 可辨识联合的高级模式,它也称做 标签联合或 代数数据类型。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:
- 具有普通的单例类型属性— 可辨识的特征。
- 一个类型别名包含了那些类型的联合— 联合。
- 此属性上的类型保护。
// 此例中type属性作为可辨识属性
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
// 可辨识联合
type Shape = Square | Rectangle | Circle;
function area(s: Square) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
// 完整性检查,不符合square,rectangle,circle类型,将报错
default: return assertNever(s);
}
}
function assertNever(x: never): never {
throw new Error('unexpected object');
}
多态的this类型
多态的 this类型表示的是某个包含类或接口的 子类型。 这被称做 F-bounded多态性。 它能很容易的表现连贯接口间的继承(链式调用)。
索引类型(Index types)
使用索引类型,编译器就能够检查是用来动态属性名的代码。
// 设置两个类型变量T、K
// T为传入的对象
// K为T的属性名称,如Person的name属性
// return T[K][], 返回对象属性值组成的数组
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n=> o[n])
}
// K extends keyof T
// 等同于
// type K = 'name' | 'age'; (联合类型)
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'Jarid',
age: 35
}
let strings: string[] = pluck(person, ['name']);
// strings = ['Jarid', 35]
- 索引类型查询操作符:keyof T (对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合)
- 索引访问操作符:T[K] (在上例中,T[K]等于Person['name'],因此为string类型)
映射类型
- 相当于是一个批量操作,比如,将一个已知的类型每个属性都变为可选的(或者readonly)
- 映射类型转换是同态的
// 原始类型
interface Person {
name: string;
age: number;
}
// 期望结果
interface Person {
name?: string;
age?: number;
}
// 映射类型
// 属性全部映射为可选的
type Partial<T> = {
[P in keyof T]? T[P];
// [P in keyof T]:提取T中的属性
// T[P]:T类型中P属性对应的类型值
}
// 属性全部映射为readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
// 使用
type PersonPartial = Partial<Person>;
type PersonReadOnly = Readonly<Person>;
最简单的映射类型
type Keys = 'option1': 'option2';
type Flags = {
[K in Keys]: boolean;
// 内部使用了for...in循环获取属性key
}
// 等同于
type Flags {
'option1': boolean;
'option2': boolean;
}
同态与非同态
- 同态——输入类型来拷贝属性,如Readonly,Partial,Pick
- 非同态——不需要输入类型来拷贝属性,如Record
利用映射,包装与拆包
// 包装
type Proxy<T> = {
get(): T;
set(value: T): void;
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
}
function proxify<T>(o:T): Proxify<T> {
}
let proxyProps = proxify(props);
// 拆包
function unproxify<T>(t: Proxify<T>): T {
let result = {} as T;
for (const k in t) {
result[k] = t[k].get();
}
return result;
}
let originalProps = unproxify(proxyProps);