缩小类型在类型守卫中;是可迭代的,但不是字符串

huangapple go评论66阅读模式
英文:

Narrow down type in type guard; is Iterable but no string

问题

基于此解决方案,我创建了一个类型守卫:

const isNoStringIterable = (obj: any): obj is Iterable<any> => {
  if (obj == null) return false;
  else if (typeof obj === "string") return false; // this isn't properly reflected by the type guard
  else return typeof obj[Symbol.iterator] === "function";
};

与链接解决方案的主要区别在于,我希望在出现string时返回false。有没有办法将类型守卫声明缩小到类似于"obj is Iterable but no string"的方式?

英文:

Based on this solution, I've created a type guard:

const isNoStringIterable = (obj: any): obj is Iterable&lt;any&gt; =&gt; {
  if (obj == null) return false;
  else if (typeof obj === &quot;string&quot;) return false; // this isn&#39;t properly reflected by the type guard
  else return typeof obj[Symbol.iterator] === &quot;function&quot;;
};

The main difference to the linked solution is that I want it to return false in case we have a string. Is there any way to narrow down the type guard declaration to something like "obj is Iterable but no string"?

答案1

得分: 1

TypeScript没有实现否定类型,正如microsoft/TypeScript#29317中所示(该功能从未合并/发布),因此没有直接的方法来表示not string,也没有直接的方法来表示Iterable&lt;any&gt; &amp; not string。相反,我们需要通过其他方式来表示"not"。

如果你的输入类型是一个联合类型,那么你可以筛选它以包括Iterable&lt;any&gt;兼容的成员,但排除string兼容的成员。联合类型的筛选并不完全等同于类型否定,但通常可以达到相同的目的。

有一些实用类型,比如Extract&lt;T, U&gt;Exclude&lt;T, U&gt;,你可以使用它们来实现这一点,但你也可以直接通过分布条件类型编写自己的实现(这就是Extract&lt;T, U&gt;Exclude&lt;T, U&gt;是如何实现的):

type NoStringIterable&lt;T&gt; =
  T extends string ? never : T extends Iterable&lt;any&gt; ? T : never;

你可以测试一下这是否按预期运行:

type Test = NoStringIterable&lt;string | number[] | Set&lt;boolean&gt; | Date&gt;;
// type Test = Set&lt;boolean&gt; | number[]

Set&lt;boolean&gt;number[]都是Iterable&lt;any&gt;,而且不是string,所以它们是输出结果。现在我们可以将isNoStringIterable作为一个泛型函数,其中输入类型为T,输出类型为obj is NoStringIterable&lt;T&gt;

const isNoStringIterable = &lt;T,&gt;(obj: T): obj is NoStringIterable&lt;T&gt; =&gt; {
  if (obj == null) return false;
  else if (typeof obj === &quot;string&quot;) return false;
  else return typeof (obj as any)[Symbol.iterator] === &quot;function&quot;;
};

让我们来测试一下:

function foo(obj: string | number[]) {
  if (isNoStringIterable(obj)) {
    obj.map(x =&gt; x + 1);
  } else {
    obj.toUpperCase();
  }
}

看起来不错。编译器会将string | number[]true块中缩小到number[],在false块中缩小到string,如预期的那样。

Playground链接到代码

英文:

TypeScript doesn't have negated types as implemented (but never merged/released) in microsoft/TypeScript#29317, so there's no direct way to say not string and thus no direct way to say Iterable&lt;any&gt; &amp; not string. Instead we need to work around it by expressing "not" in some other fashion.

If your input type is a union, then you can filter it to include Iterable&lt;any&gt;-compatible members but exclude string-compatible members. Union filtering isn't exactly the same as type negation, but it often serves the same purpose.

There are utility types like Extract&lt;T, U&gt; and Exclude&lt;T, U&gt; you can use to do this, but you also can write your own via a distributive conditional type directly (which is how Extract&lt;T, U&gt; and Exclude&lt;T, U&gt; are implemented):

type NoStringIterable&lt;T&gt; =
  T extends string ? never : T extends Iterable&lt;any&gt; ? T : never;

You can test that this behaves as expected:

type Test = NoStringIterable&lt;string | number[] | Set&lt;boolean&gt; | Date&gt;;
// type Test = Set&lt;boolean&gt; | number[]

Both Set&lt;boolean&gt; and number[] are Iterable&lt;any&gt; and not string, so that's what comes out. And now we can make isNoStringIterable a generic function where its input is of type T and its output is obj is NoStringIterable&lt;T&gt;:

const isNoStringIterable = &lt;T,&gt;(obj: T): obj is NoStringIterable&lt;T&gt; =&gt; {
  if (obj == null) return false;
  else if (typeof obj === &quot;string&quot;) return false;
  else return typeof (obj as any)[Symbol.iterator] === &quot;function&quot;;
};

Let's test it out:

function foo(obj: string | number[]) {
  if (isNoStringIterable(obj)) {
    obj.map(x =&gt; x + 1);
  } else {
    obj.toUpperCase();
  }
}

Looks good. The compiler narrows string | number[] to number[] in the true block and to string in the false block, as expected.

Playground link to code

huangapple
  • 本文由 发表于 2023年3月9日 21:51:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75685535.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定