Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript 演化史 -- 10】更好的空值检查 和 混合类 #170

Open
husky-dot opened this issue Dec 25, 2019 · 0 comments
Open

TypeScript 演化史 -- 10】更好的空值检查 和 混合类 #170

husky-dot opened this issue Dec 25, 2019 · 0 comments

Comments

@husky-dot
Copy link
Owner

husky-dot commented Dec 25, 2019

作者:Marius Schulz
译者:前端小智
来源:https://mariusschulz.com/

个人专栏 ES6 深入浅出已上线,深入ES6 ,通过案例学习掌握 ES6 中新特性一些使用技巧及原理,持续更新中,←点击可订阅。

更好地检查表达式的操作数中的 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("前端小智")

// 现在,咱们可以同时从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 中不行的,因此在 TypeScript中也不行。


原文:
https://mariusschulz.com/blog/null-checking-for-expression-operands-in-typescript
https://mariusschulz.com/blog/mixin-classes-in-typescript

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


交流

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

https://github.com/qq449245884/xiaozhi

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant