TypeScript 类型收窄与基于控制流的类型分析

在上一篇
TypeScript 的三类声明: 命名空间, 类型与值

中, 简要介绍了这三类声明与各种实体声明之间的关系, 也是连接 TypeScript 和 JavaScript 的关键. 如果说理解这三类声明能够让我们可以在 TypeScript 中灵活地桥接现有 JavaScript 代码, 这一篇文章的内容则是在 TypeScript 中写出像原生 JavaScript 一样灵活的代码的尝试.

类型收窄

在很久很久以前, TypeScript 出现了联合类型 (Union Type):

type foo = string | RegExp;

随联合类型增加的还有类型收窄 (Type Guard):

if (typeof foo === 'string') {
    // 在这个分支中, `foo` 的类型为 `string`.
} else {
    // 在这个分支中, `foo` 的类型为 `RegExp`.
}

但这时候的类型收窄有很大的局限性: 首先, 类型收窄只在 if…else?: 三元运算符中有效; 其次, 由于 TypeScript 类型系统 (截至目前) 只关注类型的 “形状”, 对于一些常见的场景, 它的行为并不 (也不该) 符合直觉:

class Validator { }

let foo: RegExp | Validator;

if (foo instanceof RegExp) {
    // 在这个分支中, `foo` 的类型为 `RegExp`.
} else {
    // 在这个分支中呢?
}

如果你的答案是 Validator 并且你是个妹纸, 那么好巧我们的直觉居然一样! 然而既然我已经做过铺垫, 答案一定不是它了. 在 else 分支中, foo 的类型为 RegExp | Validator , 再想一下, 这种设定是合理的: 因为一个不为 RegExp 实例的对象完全可以拥有和 RegExp 相同的 “形状”.

更新: 在较新的 TypeScript 中, instanceof 行为有变化, 现在 TypeScript 会认为如果 instanceoffalse , 则该值既不会具有对应的类型, 也不会符合其形状.

于是在当时, 只能通过将 else 替换为冗余的 else if (foo instanceof Validator) , 或者在 else 分支中做类型转换. 直到后来新增了自定义的类型收窄函数:

let foo: RegExp | Validator;

if (isRegExp(foo)) {
    // 在这个分支中, `foo` 的类型为 `RegExp`.
} else {
    // 在这个分支中, `foo` 的类型为 `Validator`.
}

function isRegExp(object: any): object is RegExp {
    return object instanceof RegExp;
}

其实还是一样的判断条件, 只是 TypeScript 把类型判断放权给用户, 只要你说它是或者不是某种类型, 我就认定了它是或不是某种类型. 对于 isRegExp 来说, TypeScript 并不关心函数的逻辑, 只是当返回 true 时, 就认为 object is RegExp , 返回 false 时, 则 object 不是 RegExp – 既不是 RegExp 的实例, 也没有 RegExp 的 “形状”. 这时候在 else 分支中, foo 可能的类型就只剩下了 Validator .

当然, 有了自定义的类型收窄函数, 很多 JavaScript 风格的 “类型” 判断也可以被用上, 比如:

interface Foo {
    yo(): void;
}

interface Bar {
    ha(): void;
}

function isFoo(object: Object): object is Foo {
    return !!object && typeof object.yo === 'function';
}

基于控制流的类型分析

自定义类型收窄函数已经是 1.6 时的特性了, 之后 1.7, 1.8, TypeScript 在类型分析上都没有实质性的增强, 不过还一直躺在 Pull Request 里的基于控制流的类型分析 (Anders 亲自操刀) 会很大程度上提高 TypeScript 在编写带联合类型的代码时的体验:

function exampleA() {
    let foo: string | number;

    foo = 'abc';
    foo; // `foo` 的类型现在为 `string`.

    foo = 123;
    foo; // `foo` 的类型现在为 `number`.
}

function exampleB(bar: string | number) {
    if (typeof bar === 'number') {
        bar = Array(bar + 1).join(' ');
    }

    // `bar` 的类型现在为 `string`.
}

function exampleC(pia: string | number) {
    if (typeof pia === 'number') {
        return;
    }

    // `pia` 的类型现在为 `string`.
}

当然, 不只是 if…else :

let x: string | number | boolean;

x = '';

while (cond) {
    x; // `x` 在这里的类型为 `string | number`.
    x = foo(x);
    x; // `x` 在这里的类型为 `number`.
}

x; // `x` 在这里的类型为 `string | number`.

4 月 23 日更新 , 目前该分支已合并到 master, 晚些时候应该就会以 `next` 标签发布在 npm 上, 大家可以通过安装最新的 `[email protected]` 包体验.

Cheers!

稿源:JavaScript in TypeScript (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » TypeScript 类型收窄与基于控制流的类型分析

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录