TypeScript学习-Advanced Types


Intersection Types

它是把多个类型组合成一个类型,它使得你使用这个类型的时候享有定义的所有类型的特征。
例如:

Person & Serializable & Loggable

即你定义的具有这个类型的对象拥有了这三种类型包括的所有成员。

function extend(first: T, second: U): T & U {
    let result = {};
    for (let id in first) {
        (result)[id] = (first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (result)[id] = (second)[id];
        }
    }
    return result;
}
class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

Union Types

Intersection Types类似于集合运算里面的并和差的关系。
Union Types代表多选一。使用(|),来分隔类型。number | string | boolean

function padLeft(value: string, padding: string | number) {
    // ...
}
let indentedString = padLeft("Hello world", true); // errors during compilation

当一个值是Union Types,我们只能使用所有类型里面公有的属性,因为其他的属性不确实是否有,所以使用,TypeScript会报错。

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

Type Guards and Differentiating Types

Union types对于建模情形是非常有用的,这种情形下,他们所带的类型的中有重叠的值。
例如:我们想知道在什么情况下是某一特定类型,这样我们就能有条件的使用它。
在一般的JavaScript中,我们判断是否存在特定的成员来判断。

let pet = getSmallPet();
// Each of these property accesses will cause an error
if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}

然而在TypeScript中只允许访问类型共有的值。
所以我们可以使用type assertion

let pet = getSmallPet();
if ((pet).swim) {
    (pet).swim();
}
else {
    (pet).fly();
}
User-Defined Type Guards

因为上面我们用了多次type assertion,所以我们需要改进。只做一次判断,就知道接下来的分支里面是什么类型。
所以TypeScript引入了 type guard
在一个函数的返回类型加上parameterName is Type

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet).swim !== undefined;
}

如果变量的类型和源类型兼容,也就是确实是 Fish | Bird,那么TypeScript会把变量的类型缩小到特定的类型,也就是我们的断言类型。

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

这样TypeScript既知道if分支里面是Fish,也知道else分支里面是Bird

typeof type guards

当我们需要做分支判断的时候,引入上面的padLeft例子。

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}'.`);
}

上面需要自己定义额外的函数来判断原始类型,这样是很痛苦的;但是这样的判断很常用,所以TypeScript提供了typeof guard type,来帮助减少工作。

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}'.`);
}

现在你只需要写成上面的样子就可以了。
typeof guard type是如下两种格式:

  • typeof x === "number"
  • typeof x !== "number"

其中属于typeof guard type原始类型只包括:number, string, boolean, symbol,你也可以自己定义比较其他的类型,但是不会被认为是typeof guard type

instanceof type guards

格式如下:
instance instanceof constructor
当使用这个保卫类型的时候,TypeScript会将变量的类型按顺序缩小到一下两个范围:

  • 如果这个构造函数的原型的类型不是any,就会缩小到这个原型类型
  • 否则返回这个构造函数签名的union types类型
interface Padder {
    getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}
class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}
function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadder("  ");
}
// Type is 'SpaceRepeatingPadder | StringPadder'
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
    padder; // type narrowed to 'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // type narrowed to 'StringPadder'
}

Nullable types

TypeScript有两个特殊类型。nullundefined,他们在没有限制的条件下能够赋值给任何类型。
如果加上--strictNullChecks选项,只能赋值给各自的类型和void

Type guards and type assertions

因为可以为空的类型(nullable)是union type,所以你需要排除null,方法和JavaScript中一样。

  • 方法一
function f(sn: string | null): string {
    if (sn == null) {
        return "default";
    }
    else {
        return sn;
    }
}
  • 方法二
function f(sn: string | null): string {
    return sn || "default";
}
  • 方法三,如果编译器不能排除null,你就需要用type assertions来手动排除。
    例如在嵌套的函数中,编译器不能追踪到所有调用(除非这个嵌套函数是立即执行的(IIFE)),尤其是这个嵌套函数作为返回的结果返回给外面的函数时。
    不知道在哪里会调用这个嵌套函数,所以在函数体执行时,也就不不知道参数的类型,所以编译器无法排除null
    手动排除方法:通过identifier! 排除null
function broken(name: string | null): string {
  function postfix(epithet: string) {
    return name.charAt(0) + '.  the ' + epithet; // error, 'name' is possibly null
  }
  name = name || "Bob";
  return postfix("great");
}
function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob";
  return postfix("great");
}

其实综上,就是非立即执行的嵌套函数在调用时,由于它存在的那个函数里已经执行完毕,所以它引用的那个函数中的变量类型以及不可知了。


Type Aliases

它能给类型创建一个名字。Aliases不会创建新类型,只会创建一个新名字来引用这个类型。
给原始类型创建别名不是很有用,尽管它能用于文档形式。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === "string") {
        return n;
    }
    else {
        return n();
    }
}

type aliases也可以是一个泛型。

type Container = { value: T };

我们能在type alias的属性中引用自己。构建树形类型。

type Tree = {
    value: T;
    left: Tree;
    right: Tree;
}

intersection types组合出一些非常高级的类型。

type LinkedList = T & { next: LinkedList };

interface Person {
    name: string;
}

var people: LinkedList;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;

但是,我们不能在别名申明语句等号右边使用这个别名。

type Yikes = Array; // error
Interfaces vs Type Aliases
  • 接口创建了一个新名字,而类型别名只是返回一个对象字面量
  • 接口可继承扩展,或被实现。类型别名不能。
    因为一个理想的软件是可扩展的。所以我们尽可能使用Interface,如果在遇到Interface表达不了的shape时,或者需要使用union typestuple type的时候,才需要使用类型别名。

String literal type

  • union,guard type,alias一起使用来获得一个类枚举的string类型。
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
  • 也用来区分真正函数和重载函数
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
    // ... code goes here ...
}
Discriminated Unions

union,guard type,alias一起使用来构建一个高级的模式:discriminated unions,也被成为agged unions,或algebraic data types

discriminated unions在函数式编程中很有用。有些编程语言自动区分unions,而TypeScript是构建在现代JavaScript上的,所以不自动区分。

discriminated unions包括以下三个要素。

  • 有着相同的,字符串字面量的属性的类型集 - the discriminant.
interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}
  • 用一个类型别名把这些类型union, - the union.
type Shape = Square | Rectangle | Circle;
  • 通过type guards来对这个相同属性名下的不通知作判断。
function area(s: Shape) {
    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;
    }
}
Exhaustiveness checking

你可能感兴趣的:(TypeScript学习-Advanced Types)