【TypeScript 演化史 -- 2】基于控制流的类型分析 和 只读属性

做者:Marius Schulzhtml

译者:前端小智前端

来源:Marius Schulzgit


阿里云最近在作活动,新老用户低至2折,真心以为很划算了,能够点击本条内容或者连接进行参与 promotion.aliyun.com/ntms/yunpar…github


基于控制流的类型分析

TypeScript 官网总结了基于控制流的类型分析:typescript

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

这是一个很深奥的解释。下面的示例演示了 TypeScript 如何理解赋值给局部变量的影响,以及如何相应地缩小该变量的类型:数组

let command: string | string[];

command = "pwd";
command.toLowerCase(); // 这里,command 的类型是 'string'

command = ["ls", "-la"];
command.join(" "); //  这里,command 的类型是 'string[]'
复制代码

注意,全部代码都位于同一个做用域内。尽管如此,类型检查器在任何给定位置都为 command 变量使用最具体的类型微信

  • 在分配了字符串 “pwd” 以后,command 变量就不多是字符串数组(联合类型中唯一的其余选项)。所以,TypeScript 将 command 做为 string 类型的变量,并容许调用toLowerCase() 方法。ide

  • 在分配了字符串数组 ["ls", "-la"] 以后,command 变量再也不被视为字符串,如今它是一个字符串数组,因此对 join 方法的也就能调用了。函数

一样因为进行了相同的控制流分析,所以如下函数在 TypeScript 2.0 也能够正确进行了类型检查:

function composeCommand(command: string | string[]): string{
  if (typeof command === 'string') {
    return command;
  }

  return command.join(' ')
}
复制代码

编译器如今知道,若是 command 参数的类型是 string,那么函数老是在 if 语句中提早返回。因为提早的退出行为,command 参数的类型在 if 语句以后被限制为string[]。所以,对 join 方法的调用将正确地检查类型。

TypeScript 2.0 以前,编译器没法推断出上面的语义。所以,没有从 command 变量的联合类型中删除字符串类型,并产生如下编译时错误:

Property 'join' does not exist on type 'string | string[]'.
复制代码

严格的 Null 检查

当与可空类型一块儿使用时,基于控制流的类型分析尤为有用,可空类型使用包括 nullundefined 在联合类型中的表示。一般,在使用可空类型的变量以前,咱们须要检查该变量是否具备非空值:

type Person = {
  firstName: string;
  lastName?: string | null | undefined;
};

function getFullName(person: Person): string {
  const { firstName, lastName } = person;

  // 在这里,咱们检查 `lastName` 属性的 虚值(falsy), 
  // 包含 `null` 和 `undefined`(以及其它值,例如 `""`)

   //包含`null`和`undefined`(以及其余值,例如“”)
  if (!lastName) {
    return firstName;
  }

  return `${firstName} ${lastName}`;
}
复制代码

在此,Person 类型定义了一个不可为空的 firstName 属性和一个可为空的 lastName 属性。 若是咱们要返回全名,则须要检查 lastNamenull 或者undefined ,以免将字符串 "null""undefined" 附加到名字上。

为了清晰可见,我将 undefined 的类型添加到 lastName 属性的联合类型中,尽管这是多余的作法。 在严格的 null 检查模式下,undefined 的类型会自动添加到可选属性的联合类型中,所以咱们没必要显式将其写出。

明确赋值分析

基于控制流的另外一个新特性是明确赋值分析。在严格的 null 检查模式下,对类型不容许为 undefined 的局部变量有明确赋值的分析:

let name: string;

// Error: 在赋值前使用了变量 “name”
console.log(name);
复制代码

该规则的一个例外是类型包括 undefined 的局部变量

let name: string | undefined;
console.log(name); // No error
复制代码

明确的赋值分析是另外一种针对可空性缺陷的保护措施。其思想是确保每一个不可空的局部变量在使用以前都已正确初始化。

只读属性

TypeScript 2.0 中,readonly 修饰符被添加到语言中。使用 readonly 标记的属性只能在初始化期间或从同一个类的构造函数中分配,其余状况一概不容许。

来看一个例子。下面是一个简单的 Point 类型,它声明了两个只读属性 xy

type Point = {
  readonly x: number;
  readonly y: number;
};
复制代码

如今,咱们能够建立一个表示原点 point(0, 0) 的对象:

const origin: Point = { x:0, y:0 };
复制代码

因为 xy 标记为 readonly,所以咱们没法更改这两个属性的值:

// 错误:赋值表达式的左侧
// 不能是常量或只读属性
origin.x = 100;
复制代码

一个更现实的例子

虽然上面的示例可能看起来有些作做(确实是这样),可是请考虑下面这样的函数:

function moveX(p: Point, offset: number): Point {
  p.x += offset;
  return p;
}
复制代码

moveX 函数不能修改给定 px 属性。由于 x 是只读的,若是尝试这么,TypeScript 编译器会给出错误提示:

相反,moveX 应该返回一个具备更新的属性值的 point,它相似这样的:

function moveX(p: Point, offset: number): Point {
  return {
    x: p.x + offset,
    y: p.y
  };
}
复制代码

只读类属性

我们还能够将 readonly 修饰符应用于类中声明的属性。以下所示,有一个 Circle 类,它有一个只读 的radius 属性和一个get area 属性,后者是隐式只读的,由于没有 setter:

class Circle {
  readonly radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  get area() {
    return Math.PI * this.radius ** 2;
  }
}
复制代码

注意,使用 ES7 指数运算符对 radius 进行平方。radiusarea 属性均可以从类外部读取(由于它们都不是私有(private)的),可是不能写入(由于它们都是只读(readonly)的):

const unitCircle = new Circle(1);
unitCircle.radius; // 1
unitCircle.area; // 3.141592653589793

// 错误:赋值表达式的左侧
// 不能是常量或只读属性
unitCircle.radius = 42;

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.area = 42;
复制代码

只读索引签名

此外,可使用 readonly 修饰符标记索引签名。ReadonlyArray<T> 类型使用这样的索引签名来阻止对索引属性的赋值:

interface ReadonlyArray<T> {
  readonly length: number;
  // ...
  readonly [n: number]: T;
}
复制代码

因为只读索引签名,编译器将如下赋值标记为无效

const primesBelow10: ReadonlyArray<number> = [2, 3, 5, 7];

// Error: 类型 “ReadonlyArray<number>” 中的索引签名仅容许读取
primesBelow10[4] = 11;
复制代码

只读与不变性

readonly 修饰符是TypeScript类型系统的一部分。它只被编译器用来检查非法的属性分配。一旦TypeScript代码被编译成JavaScript,全部readonly的概念都消失了。您能够随意摆弄这个小示例,看看如何转换只读属性。

由于 readonly 只是一个编译时工件,因此没有针对运行时的属性分配的保护。也就是说,它是类型系统的另外一个特性,经过让编译器从 TypeScript 代码库中检查意外的属性分配,帮助你编写正确的代码。

总结

基于控制流的类型分析是 TypeScript 类型系统的一个强大的补充。类型检查器如今理解了控制流中赋值和跳转的语义,从而大大减小了对类型保护的须要。能够经过消除 nullundefined 类型来简化可空变量的处理。最后,控制流分析防止引用在给定位置没有明确分配的变量。

代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

原文:mariusschulz.com/blog/contro…

原文:mariusschulz.com/blog/read-o…

交流(欢迎加入群,群工做日都会发红包,互动讨论技术)

阿里云最近在作活动,新老用户低至2折,真心以为很划算了,能够点击本条内容或者连接进行参与 promotion.aliyun.com/ntms/yunpar…

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

github.com/qq449245884…

由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。

相关文章
相关标签/搜索