【问题标题】:How to write type guards with no-unsafe-any enabled?如何编写启用 no-unsafe-any 的类型保护?
【发布时间】:2019-07-03 20:20:25
【问题描述】:

我正在尝试通过使用更严格的 lint 规则集来收紧我的 TS 代码,但我正在努力解决什么应该是合法的动态用途。

我正在制作一个类型保护来检测某些东西是否是可迭代的(如果不是,则将其包装在一个数组中),除了抑制 lint 规则以告诉它是 kosher 之外,我不知道要告诉 TS 什么:

function isIterable(obj: any): obj is Iterable<unknown> {
    return obj && typeof obj[Symbol.iterator] === 'function';
}

我尝试将其更改为:

function isIterable(obj: undefined | {[Symbol.iterator]?: unknown}): obj is Iterable<unknown> {
    return !!obj && typeof obj[Symbol.iterator] === 'function';
}

它编译时不使用any,但它没有用,因为我想将未知类型的值传递给它。

是否有一种“干净”的方式来表示“是的,我实际上想依靠 JS 返回 undefined 来访问对象上不存在的属性”?特别是。因为这是编写类型保护的重点。

【问题讨论】:

  • isIterable(obj: unknown): obj is Iterable&lt;unknown&gt;?
  • @jcalz 在没有类型断言的情况下使用unknown 做任何事情都是不合法的。在不打开suppressImplicitAnyIndexErrors 的情况下访问object 上的任意属性也是不合法的;即使那样你也不能做 obj.foo,而且 linter 会抱怨 obj['foo']。 (它适用于 obj[Symbol.whatever],这是针对上述情况的一种解决方法,但不适用于我想通过字符串命名的键来缩小类型的其他人,即 'then' 用于承诺。)
  • 是的,我明白你现在在说什么了。

标签: typescript tslint


【解决方案1】:

我不知道在user-defined type guard实现中,像 no-unsafe-any 这样的东西是否会让你买太多,因为通常这种类型保护的全部意义在于允许编译器通过内置的控制流缩小来缩小通常无法做到的值。我当然会理解在这样的实现中暂停一条 linter 规则。

但我认为您几乎可以像这样获得您正在寻找的行为:

function isIterable(obj: unknown): obj is Iterable<unknown> {
  if ((typeof obj !== 'object') || (obj === null)) return false; 
  // obj is now type object
  const wObj: { [Symbol.iterator]?: unknown } = obj; // safely widen to wObj
  return typeof wObj[Symbol.iterator] === 'function'; 
}

这是一些需要跳过的环节,但我们的想法是使用控制流缩小将unknown缩小到object,然后将object专门扩大到具有您要检查的可选属性的类型(这通过引入一个新变量来实现)。最后,检查扩展类型上该属性的类型。由于您要检查的属性键是符号类型,因此您需要在扩展类型中提及特定的属性名称。如果属性键是字符串,您可以使用string index signature

function isPromise(obj: unknown): obj is Promise<unknown> {
  if ((typeof obj !== 'object') || (obj === null)) return false;
  // obj is now type object
  const wObj: {[k: string]: unknown} = obj; // safely widen to wObj
  return typeof wObj.then === 'function';
}

无论如何,我希望这能让你更接近你的目标。祝你好运!

【讨论】:

  • 老实说,安全的加宽步骤可能是我在这里遇到的问题,谢谢!
【解决方案2】:

另一个好的策略是使用Partialas 演员表。

interface RegularForm {
    regular: number;
}

interface FancyForm extends RegularForm {
    fancy: string;
}

const isFancyForm = (instance: RegularForm): instance is FancyForm =>
    (instance as Partial<FancyForm>).fancy !== undefined;

【讨论】:

  • 我想尽可能地避开as断言;发现你可以通过使用type Unknown&lt;T&gt; = {[k in keyof T]?: unknown}; 之类的东西来实现这一点。正如@jcalz 提示我的那样,在没有类型断言的情况下为其分配任何东西是合法的,并且它准确地反映了您对该对象的了解 - 它可能具有也可能没有这些属性,并且您不知道它们的类型是什么它确实有它们。
  • 遗憾的是,如果您正在寻找符号键属性,则两者都不会正常工作,因为映射类型由于某种原因不会超过这些。
猜你喜欢
  • 2019-02-22
  • 2019-06-19
  • 1970-01-01
  • 1970-01-01
  • 2021-05-19
  • 1970-01-01
  • 1970-01-01
  • 2020-02-16
  • 2019-12-30
相关资源
最近更新 更多