英文:
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<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";
};
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<any> & not string
。相反,我们需要通过其他方式来表示"not"。
如果你的输入类型是一个联合类型,那么你可以筛选它以包括Iterable<any>
兼容的成员,但排除string
兼容的成员。联合类型的筛选并不完全等同于类型否定,但通常可以达到相同的目的。
有一些实用类型,比如Extract<T, U>
和Exclude<T, U>
,你可以使用它们来实现这一点,但你也可以直接通过分布条件类型编写自己的实现(这就是Extract<T, U>
和Exclude<T, U>
是如何实现的):
type NoStringIterable<T> =
T extends string ? never : T extends Iterable<any> ? T : never;
你可以测试一下这是否按预期运行:
type Test = NoStringIterable<string | number[] | Set<boolean> | Date>;
// type Test = Set<boolean> | number[]
Set<boolean>
和number[]
都是Iterable<any>
,而且不是string
,所以它们是输出结果。现在我们可以将isNoStringIterable
作为一个泛型函数,其中输入类型为T
,输出类型为obj is NoStringIterable<T>
:
const isNoStringIterable = <T,>(obj: T): obj is NoStringIterable<T> => {
if (obj == null) return false;
else if (typeof obj === "string") return false;
else return typeof (obj as any)[Symbol.iterator] === "function";
};
让我们来测试一下:
function foo(obj: string | number[]) {
if (isNoStringIterable(obj)) {
obj.map(x => x + 1);
} else {
obj.toUpperCase();
}
}
看起来不错。编译器会将string | number[]
在true
块中缩小到number[]
,在false
块中缩小到string
,如预期的那样。
英文:
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<any> & 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<any>
-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<T, U>
and Exclude<T, U>
you can use to do this, but you also can write your own via a distributive conditional type directly (which is how Extract<T, U>
and Exclude<T, U>
are implemented):
type NoStringIterable<T> =
T extends string ? never : T extends Iterable<any> ? T : never;
You can test that this behaves as expected:
type Test = NoStringIterable<string | number[] | Set<boolean> | Date>;
// type Test = Set<boolean> | number[]
Both Set<boolean>
and number[]
are Iterable<any>
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<T>
:
const isNoStringIterable = <T,>(obj: T): obj is NoStringIterable<T> => {
if (obj == null) return false;
else if (typeof obj === "string") return false;
else return typeof (obj as any)[Symbol.iterator] === "function";
};
Let's test it out:
function foo(obj: string | number[]) {
if (isNoStringIterable(obj)) {
obj.map(x => 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论