1. 首页

【TypeScript 进化史 — 10】更好的空值检查 和 混合类

更好地检查表达式的操作数中的 null/undefined

在TypeScript 2.2中,空检查得到了进一步的改进。TypeScript 现在将带有可空操作数的表达式标记为编译时错误。

具体来说,下面这些会被标记为错误:

  • 如果+运算符的任何一个操作数是可空的,并且两个操作数都不是anystring类型。
  • 如果-***/%<<>>>>>, &, |^运算符的任何一个操作数是可空的。
  • 如果 <><=>=in 运算符的任何一个操作数是可空的。
  • 如果 instanceof 运算符的右操作数是可空的。
  • 如果一元运算符+-~++或者--的操作数是可空的。

来看看如果咱们不小心,可空表达式操作数就会坑下咱们的情况。在 TypeScript 2.2 之前,下面这个函数是可以很好地编译通过的:


function isValidPasswordLength( password: string, min: number, max?: number ) { return password.length >= min && password.length <= max; }

注意max参数是可选的。这意味着咱们可以使用两个或三个参数来调用isValidPasswordLength


isValidPasswordLength("open sesame", 6, 128); // true isValidPasswordLength("open sesame", 6, 8); // false

密码 "open sesame"的长度为10个字符。因此,对于长度范围 [6,128] 返回 true,对于长度范围[6,8]返回false,到目前为止,一切 ok。

如果调用isValidPasswordLength且不提供max参数值,那么当密码长度超过 min 值时,咱们可能希望返回 true。然而,事实并非如此:


isValidPasswordLength("open sesame", 6); // false

这里的问题在于 <= max 比较。如果maxundefined,那么 <= max 的值永远都为false。在这种情况下,isValidPasswordLength将永远不会返回true

在 TypeScript 2.2 中,表达式password.length <= max不正确的类型,如果你的应用程序正在严格的null检查模式下运行:


function isValidPasswordLength( password: string, min: number, max?: number ) { return password.length >= min && password.length <= max; // Error: 对象可能为“未定义”. }

如果操作数的类型是nullundefined或者包含nullundefined的联合类型,则操作数视为可空的。

注意:包含nullundefined的联合类型只会出现在--strictNullChecks模式中,因为常规类型检查模式下nullundefined在联合类型中是不存在的。

那么要怎么修正这个问题呢?一种的解决方案是为max参数提供一个默认值,它只在传递undefined 时起作用。这样,该参数仍然是可选的,但始终包含类型为number的值


function isValidPasswordLength( password: string, min: number, max: number = Number.MAX_VALUE ) { return password.length >= min && password.length <= max; }

当然咱们也可以选择其他的方法,但是我觉得这个方法很好。只要不再将maxundefined 的值进行比较,就可以了

混合类

TypeScript 的一个目的是支持不同框架和库中使用的通用 JS 模式。从TypeScript 2.2开始,增加了对 ES6 混合类(mixin class)模式。接下来讲讲 mixin 是什么,然后举例说明了如何在 TypeScript 中使用它们。

JavaScript/TypeScript中的 mixin

混合类是实现不同功能方面的类。其他类可以包含 mixin 并访问它的方法和属性。这样,mixin 提供了一种基于组合行为的代码重用形式。

混合类指一个extends(扩展)了类型参数类型的表达式的类声明或表达式. 以下规则对混合类声明适用:

  • extends表达式的类型参数类型必须是混合构造函数.
  • 混合类的构造函数 (如果有) 必须有且仅有一个类型为any[]的变长参数, 并且必须使用展开运算符在super(...args)调用中将这些参数传递。

定义完成之后,来研究一些代码。下面是一个 Timestamped 函数,它在timestamp 属性中跟踪对象的创建日期:


type Constructor<T = {}> = new (..args: any[]) => T; function Timestamped<TBase extends Constructor>(Base: TBase) { return class extends Base { timestamp = Date.now() } }

这看起来有点复杂,咱们一行一行来看看:


type Constructor<T = {}> = new (..args: any[]) => T;

type Constructor <T>是构造签名的别名,该签名描述了可以构造通用类型T的对象的类型,并且其构造函数接受任意数量的任何类型的参数。

接下来,让我们看一下mixin函数本身:


function Timestamped<TBase extends Constructor>(Base: TBase) { return class extends Base { timestamp = Date.now(); }; }

Timestamped 函数接受一个名为Base的参数,该参数属于泛型类型 TBase注意TBase 必须与Constructor兼容,即类型必须能够构造某些东西。

在函数体中,咱们创建并返回一个派生自Base的新类。这种语法乍一看可能有点奇怪。咱们创建的是类表达式,而不是类声明,后者是定义类的更常用方法。咱们的新类定义了一个timestamp的属性,并立即分配自UNIX时代以来经过的毫秒数。

注意,从mixin函数返回的类表达式是一个未命名的类表达式,因为class关键字后面没有名称。与类声明不同,类表达式不必命名。咱们可以选择添加一个名称,它将是类主体的本地名称,并允许类引用自己


function Timestamped<TBase extends Constructor>(Base: TBase) { return class Timestamped extends Base { timestamp = Date.now(); }; }

现在已经介绍了两个类型别名和mixin函数的声明,接下来看看如何在另一个类中使用 mixin:

class User {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

// 通过将`"Timestamped"`混合到"User"中创建一个新类
const TimestampedUser = Timestamped(User);

// 实例化新的 "TimestampedUser" 类
const user = new TimestampedUser("JS中文网 www.javascriptc.com")

// 现在,咱们可以同时从User 类中访问属性
// 也可以从 Timestamped 类中访问属性
console.log(user.name);
console.log(user.timestamp);

TypeScript 编译器知道我们在这里创建并使用了一个mixin,一切都是完全静态类型的,并且会自动完成和重构。

混合构造函数

现在,看看一个稍微高级一点的 mixin,类中定义一个构造函数


function Tagged<TBase extends Constructor>(Base: TBase) { return class extends Base { tag: string | null; constructor(...args: any[]) { super(...args); this.tag = null; } }; }

如果在混合类中定义构造函数,那么它必须有一个类型为any[]rest参数。这样做的原因是,mixin不应该绑定到具有已知构造函数参数的特定类;因此,mixin应该接受任意数量的任意值作为构造函数参数。所有参数都传递给Base的构造函数,然后mixin执行它的任务。在咱们的例子中,它初始化 tag 属性。

混合构造函数类型指仅有单个构造函数签名,且该签名仅有一个类型为 any[] 的变长参数,返回值为对象类型. 比如, 有 X 为对象类型, new (...args: any[]) => X 是一个实例类型为 X 的混合构造函数类型。

以前面使用Timestamped的相同方式来使用混合Tagged


// 通过 User 作为混合 Tagged 来创建一个新类 const TaggedUser = Tagged(User); // 实例化 "TaggedUser" 类 const user = new TaggedUser("John Doe"); // 现在,可以从 User 类访问属性和 Tagged 中的属性 user.name = "Jane Doe"; user.tag = "janedoe";

mixin 与方法

到目前为止,咱们只在mixin中添加了数据属性。现在来看看另一个 mixin,它额外实现了两个方法:


fucntion Activatable<TBase extends Constructor>(Base: TBase) { return class extends Base { isActivated = false; activate() { this.isActivated = true; } deactivate() { this.isActivated = false; } } }

咱们从mixin函数返回一个常规的类。这意味着咱们可以使用所有受支持的类功能,例如构造函数,属性,方法,getter/setter,静态成员等。

如何所示,咱们如何在 User 类中使用混合的 Activatable


const ActivatableUser = Activatable(User); // 实例化新的"ActivatableUser"类 const user = new ActivatableUser("John Doe"); //初始化,isActivated 的值为 false console.log(user.isActivated); user.activate(); console.log(user.isActivated); // true

组合多个mixin

组合的mixin,可以让它更加灵活。一个类可以包含任意多的mixin,为了演示这点,咱们把上面提到的所有mixin 代码组合在一起。


const SpecialUser = Activatable(Tagged(Timestamped(User))); const user = new SpecialUser("John Doe");

当然 SpecialUser类不一定非常有用,但关键是,TypeScript静态地理解这种mixin组合。编译器可以类型检查所有的使用,并在自动完成列表中建议可用的成员:
Js中文网 · 前端进阶资源教程 www.javascriptc.com

与类继承进行对比,有个区别:一个类只能有一个基类。继承多个基类在 JS 中不行的,因此在 TypeScript中也不行。


往期阅读

作者:Marius Schulz
链接:https://mariusschulz.com/blog/mixin-classes-in-typescript

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:【TypeScript 进化史 — 10】更好的空值检查 和 混合类

链接:https://www.javascriptc.com/3348.html

« 请问如何正确的使用useReducer Hook
程序员升级打怪[2019]:前端性能优化和框架»
Flutter 中文教程资源

相关推荐

QR code