TypeScript 2.0介绍


本文整理自网络,侵删。

TypeScript 2.0介绍

Null和undefined类型

TypeScript现在有两个特殊的类型:Null和Undefined, 它们的值分别是nullundefined。 以前这是不可能明确地命名这些类型的,但是现在 nullundefined不管在什么类型检查模式下都可以作为类型名称使用。

以前类型检查器认为nullundefined赋值给一切。实际上,nullundefined是每一个类型的有效值, 并且不能明确排除它们(因此不可能检测到错误)。

--strictNullChecks

--strictNullChecks可以切换到新的严格空检查模式中。

在严格空检查模式中,nullundefined不再属于任何类型的值,仅仅属于它们自己类型和any类型的值 (还有一个例外, undefined也能赋值给void)。因此,尽管在常规类型检查模式下TT | undefined被认为是相同的 (因为 undefined被认为是任何T的子类型),但是在严格类型检查模式下它们是不同的, 并且仅仅 T | undefined允许有undefined值,TT | null的关系同样如此。

示例

// 使用--strictNullChecks参数进行编译的
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1;  // 正确
y = 1;  // 正确
z = 1;  // 正确
x = undefined;  // 错误
y = undefined;  // 正确
z = undefined;  // 正确
x = null;  // 错误
y = null;  // 错误
z = null;  // 正确
x = y;  // 错误
x = z;  // 错误
y = x;  // 正确
y = z;  // 错误
z = x;  // 正确
z = y;  // 正确

使用前赋值检查

在严格空检查模式中,编译器要求未包含undefined类型的局部变量在使用之前必须先赋值。

示例

// 使用--strictNullChecks参数进行编译
let x: number;
let y: number | null;
let z: number | undefined;
x;  // 错误,使用前未赋值
y;  // 错误,使用前未赋值
z;  // 正确
x = 1;
y = null;
x;  // 正确
y;  // 正确

编译器通过执行基于控制流的类型分析检查变量明确被赋过值。在本篇文章后面会有进一步的细节。

可选参数和属性

可选参数和属性会自动把undefined添加到他们的类型中,即使他们的类型注解明确不包含undefined。例如,下面两个类型是完全相同的:

// 使用--strictNullChecks参数进行编译
type T1 = (x?: number) => string;              // x的类型是 number | undefined
type T2 = (x?: number | undefined) => string;  // x的类型是 number | undefined

非null和非undefined类型保护

如果对象或者函数的类型包含nullundefined,那么访问属性或调用函数时就会产生编译错误。因此,对类型保护进行了扩展,以支持对非null和非undefined的检查。

示例

// 使用--strictNullChecks参数进行编译
declare function f(x: number): string;
let x: number | null | undefined;
if (x) {
    f(x);  // 正确,这里的x类型是number
}
else {
    f(x);  // 错误,这里的x类型是number?
}
let a = x != null ? f(x) : "";  // a的类型是string
let b = x && f(x);  // b的类型是 string | 0 | null | undefined

非null和非undefined类型保护可以使用==!====!==操作符和nullundefined进行比较,如x != nullx === undefined。对被试变量类型的影响准确地反映了JavaScript的语义(比如,双等号运算符检查两个值无论你指定的是null还是undefined,然而三等于号运算符仅仅检查指定的那一个值)。

类型保护中的点名称

类型保护以前仅仅支持对局部变量和参数的检查。现在类型保护支持检查由变量或参数名称后跟一个或多个访问属性组成的“点名称”。

示例

interface Options {
    location?: {
        x?: number;
        y?: number;
    };
}

function foo(options?: Options) {
    if (options && options.location && options.location.x) {
        const x = options.location.x;  // x的类型是number
    }
}

点名称的类型保护和用户定义的类型保护函数,还有typeofinstanceof操作符一起工作,并且不依赖--strictNullChecks编译参数。

对点名称进行类型保护后给点名称任一部分赋值都会导致类型保护无效。例如,对x.y.z进行了类型保护后给xx.yx.y.z赋值,都会导致x.y.z类型保护无效。

表达式操作符

表达式操作符允许运算对象的类型包含null和/或undefined,但是总是产生非null和非undefined类型的结果值。

// 使用--strictNullChecks参数进行编译
function sum(a: number | null, b: number | null) {
    return a + b;  // 计算的结果值类型是number
}

&&操作符添加null和/或undefined到右边操作对象的类型中取决于当前左边操作对象的类型,||操作符从左边联合类型的操作对象的类型中将nullundefined同时删除。

// 使用--strictNullChecks参数进行编译
interface Entity {
    name: string;
}
let x: Entity | null;
let s = x && x.name;  // s的类型是string | null
let y = x || { name: "test" };  // y的类型是Entity

类型扩展

在严格空检查模式中,nullundefined类型是不会扩展到any类型中的。

let z = null;  // z的类型是null

在常规类型检查模式中,由于扩展,会推断z的类型是any,但是在严格空检查模式中,推断znull类型(因此,如果没有类型注释,nullz的唯一值)。

非空断言操作符

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符!可以用于断言操作对象是非null和非undefined类型的。具体而言,运算x!产生一个不包含nullundefinedx的值。断言的形式类似于<T>xx as T!非空断言操作符会从编译成的JavaScript代码中移除。

// 使用--strictNullChecks参数进行编译
function validateEntity(e?: Entity) {
    // 如果e是null或者无效的实体,就会抛出异常
}

function processEntity(e?: Entity) {
    validateEntity(e);
    let s = e!.name;  // 断言e是非空并访问name属性
}

兼容性

这些新特性是经过设计的,使得它们能够在严格空检查模式和常规类型检查模式下都能够使用。尤其是在常规类型检查模式中,nullundefined类型会自动从联合类型中删除(因为它们是其它所有类型的子类型),!非空断言表达式操作符也被允许使用但是没有任何作用。因此,声明文件使用null和undefined敏感类型更新后,在常规类型模式中仍然是可以向后兼容使用的。

在实际应用中,严格空检查模式要求编译的所有文件都是null和undefined敏感类型。

基于控制流的类型分析

TypeScript 2.0实现了对局部变量和参数的控制流类型分析。以前,对类型保护进行类型分析仅限于if语句和?:条件表达式,并且不包括赋值和控制流结构的影响,例如returnbreak语句。使用TypeScript 2.0,类型检查器会分析语句和表达式所有可能的控制流,在任何指定的位置对声明为联合类型的局部变量或参数产生最可能的具体类型(缩小范围的类型)。

示例

function foo(x: string | number | boolean) {
    if (typeof x === "string") {
        x; // 这里x的类型是string
        x = 1;
        x; // 这里x的类型是number
    }
    x; // 这里x的类型是number | boolean
}

function bar(x: string | number) {
    if (typeof x === "number") {
        return;
    }
    x; // 这里x的类型是string
}

基于控制流的类型分析在--strictNullChecks模式中尤为重要,因为可空类型使用联合类型来表示:

function test(x: string | null) {
    if (x === null) {
        return;
    }
    x; // 在函数的剩余部分中,x类型是string
}

而且,在--strictNullChecks模式中,基于控制流的分析包括,对类型不允许为undefined的局部变量有明确赋值的分析。

function mumble(check: boolean) {
    let x: number; // 类型不允许为undefined
    x; // 错误,x是undefined
    if (check) {
        x = 1;
        x; // 正确
    }
    x; // 错误,x可能是undefi
    x = 2;
    x; // 正确
}

标记联合类型

TypeScript 2.0实现了标记(或区分)联合类型。具体而言,TS编译器现在支持类型保护,基于判别属性的检查来缩小联合类型的范围,并且switch语句也支持此特性。

示例

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: Shape) {
    // 在下面的switch语句中,s的类型在每一个case中都被缩小
    // 根据判别属性的值,变量的其它属性不使用类型断言就可以被访问
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}

function test1(s: Shape) {
    if (s.kind === "square") {
        s;  // Square
    }
    else {
        s;  // Rectangle | Circle
    }
}

function test2(s: Shape) {
    if (s.kind === "square" || s.kind === "rectangle") {
        return;
    }
    s;  // Circle
}

判别属性类型保护x.p == vx.p === vx.p != v或者x.p !== v其中的一种表达式,pv是一个属性和字符串字面量类型或字符串字面量联合类型的表达式。判别属性类型保护缩小x的类型到由判别属性pv的可能值之一组成的类型。

请注意,我们目前只支持字符串字面值类型的判别属性。我们打算以后添加对布尔值和数字字面量类型的支持。

never类型

TypeScript 2.0引入了一个新原始类型nevernever类型表示值的类型从不出现。具体而言,never是永不返回函数的返回类型,也是变量在类型保护中永不为true的类型。

never类型具有以下特征:

  • never是所有类型的子类型并且可以赋值给所有类型。
  • 没有类型是never的子类型或能赋值给nevernever类型本身除外)。
  • 在函数表达式或箭头函数没有返回类型注解时,如果函数没有return语句,或者只有never类型表达式的return语句,并且如果函数是不可执行到终点的(例如通过控制流分析决定的),则推断函数的返回类型是never
  • 在有明确never返回类型注解的函数中,所有return语句(如果有的话)必须有never类型的表达式并且函数的终点必须是不可执行的。

因为never是每一个类型的子类型,所以它总是在联合类型中被省略,并且在函数中只要其它类型被返回,类型推断就会忽略never类型。

一些返回never函数的示例:

// 函数返回never必须无法执行到终点
function error(message: string): never {
    throw new Error(message);
}

// 推断返回类型是never
function fail() {
    return error("Something failed");
}

// 函数返回never必须无法执行到终点
function infiniteLoop(): never {
    while (true) {
    }
}

一些函数返回never的使用示例:

// 推断返回类型是number
function move1(direction: "up" | "down") {
    switch (direction) {
        case "up":
            return 1;
        case "down":
            return -1; 
    }
    return error("Should never get here");
}

// 推断返回类型是number
function move2(direction: "up" | "down") {
    return direction === "up" ? 1 :
        direction === "down" ? -1 :
        error("Should never get here");
}

// 推断返回类型是T
function check<T>(x: T | undefined) {
    return x || error("Undefined value");
}

因为never可以赋值给每一个类型,当需要回调函数返回一个更加具体的类型时,函数返回never类型可以用于检测返回类型是否正确:

function test(cb: () => string) {
    let s = cb();
    return s;
}

test(() => "hello");
test(() => fail());
test(() => { throw new Error(); })

只读属性和索引签名

属性或索引签名现在可以使用readonly修饰符声明为只读的。

只读属性可以初始化和在同一个类的构造函数中被赋值,但是在其它情况下对只读属性的赋值是不允许的。

此外,有几种情况下实体隐式只读的:

  • 属性声明只使用get访问器而没有使用set访问器被视为只读的。
  • 在枚举类型中,枚举成员被视为只读属性。
  • 在模块类型中,导出的const变量被视为只读属性。
  • import语句中声明的实体被视为只读的。
  • 通过ES2015命名空间导入访问的实体被视为只读的(例如,当foo当作import * as foo from "foo"声明时,foo.x是只读的)。

示例

interface Point {
    readonly x: number;
    readonly y: number;
}

var p1: Point = { x: 10, y: 20 };
p1.x = 5;  // 错误,p1.x是只读的

var p2 = { x: 1, y: 1 };
var p3: Point = p2;  // 正确,p2的只读别名
p3.x = 5;  // 错误,p3.x是只读的
p2.x = 5;  // 正确,但是因为别名使用,同时也改变了p3.x
class Foo {
    readonly a = 1;
    readonly b: string;
    constructor() {
        this.b = "hello";  // 在构造函数中允许赋值
    }
}
let a: Array<number> = [0, 1, 2, 3, 4];
let b: ReadonlyArray<number> = a;
b[5] = 5;      // 错误,元素是只读的
b.push(5);     // 错误,没有push方法(因为这会修改数组)
b.length = 3;  // 错误,length是只读的
a = b;         // 错误,缺少修改数组的方法

指定函数中this类型

紧跟着类和接口,现在函数和方法也可以声明this的类型了。

阅读剩余部分

相关阅读 >>

typescript 基础类型

typescript 构建工具整合

typescript 编译选项

vue刷新404的问题解决方法

angular实现只执行正在开发的新单元测试

聊聊typescript中enum(枚举)的用法

如何从javascript到typescript

typescript 高级类型

typescript 快速入门

typescript 声明文件规范

更多相关阅读请进入《typescript》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...