TypeScript学习-Advanced Types


Intersection Types

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

Person & Serializable & Loggable

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

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() {
        // ...
    }
}
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 ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}
User-Defined Type Guards

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

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>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<T> = { value: T };

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

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

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

type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;
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<Yikes>; // 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容