英文:
type checking that value is not one of several values
问题
在 TypeScript 中,你可以使用枚举作为类型,以确保变量的值在枚举中。例如:
type children = "Andy" | "Bob";
export const func = (name: children): void => {
...
};
然而,我希望有一种功能来确保变量的值不在枚举(或任何其他类型的集合)中。我知道可以通过简单的 if 语句/for 循环来实现这一点,但我希望当变量不是某些值之一时,会抛出类似类型错误的东西。是否有一种类似于枚举的反向功能,可以用来检查变量名是否不是几个特定值之一?在上面的示例中,我希望确保 name
的值不是 Andy
或 Bob
。
英文:
In typescript you can use an enum as a type in order to ensure that a variable has a value that is within the enum. For example:
type children = "Andy" | "Bob"
export const func = (name: children) : void => {
...
};
However, I wish for functionality that ensures the value of a variable is NOT within an enum (or any other kind of collection). I know this can be achieved with simple if statements/for loops but I'm hoping that when the variable is not one of certain values, something like a type error will be thrown. Is there something like the opposite of an enum that I can use to check a variable name is NOT one of several values? In the above example, I would want it so that the value of name
is ensured not to be Andy
or Bob
.
答案1
得分: 2
TypeScript缺乏否定类型(类似于microsoft/TypeScript#29317中实现的类型,但从未合并到语言中),因此没有与"所有不是Children
的string
"对应的特定类型。如果有的话,你可以写成type NotChildren = string & not Children
,然后只需使用(name: NotChildren) => {}
。相反,我们将需要通过使用泛型来模拟它们。
我将采取的方法是观察泛型类型T
,该类型已经受到了约束,只能为string
时,只有当T
和Children
之间完全没有重叠时,才应拒绝它。也就是说,T
和Children
的交集应该是空的/不可能的never
类型。因此,让我们使func
在T
上具有泛型,并且给name
赋予条件类型(T & Children) extends never ? T : never
,如果T & Children
没有重叠,则评估为T
,否则为never
:
type Children = "Andy" | "Bob";
export const func = <T extends string>(
name: (T & Children) extends never ? T : never
): void => { };
基本用法如下:
func("Andy"); // error;
// const func: <"Andy">(name: never) => void
func("Bob"); // error
// const func: <"Bob">(name: never) => void
func("Charie"); // okay;
// const func: <"Charie">(name: "Charie") => void
前两个调用失败,因为当T
被推断为文字类型"Andy"
或"Bob"
时,T & Children
是可能的,因此(T & Children) extends never
为false,因此(T & Children) extends never ? T : never
为never
,因此拒绝了这些参数。
第二个调用成功,因为当T
被推断为文字类型"Charlie"
时,T & Children
是不可能的,因此(T & Children) extends never
为true,因此(T & Children) extends never ? T : never
为T
,因此接受了参数。
到目前为止看起来很好。
现在让我们考虑一些更复杂的情况:
func(Math.random() < 0.5 ? "Andy" : "Charlie"); // error
// const func: <"Andy" | "Charlie">(name: never) => void
这失败了,因为参数要么是"Andy"
,要么是"Charlie"
,并且因为Children
与该类型之间存在一些重叠(即"Andy"
),所以它被拒绝了。
任意的string
也被拒绝:
function foo(x: string) {
func(x); // error, maybe it's "Andy" or "Bob"
// const func: <string>(name: never) => void
}
因为string
与Children
之间存在重叠(即Children
),所以它被拒绝了。请注意,编译器和人类对于什么构成“任意”字符串可能有不同的看法。如果使用加号运算符来连接字符串,编译器不会尝试弄清楚可能是什么,而只会将其扩展为string
。因此,这被拒绝了:
func("Char" + "lie"); // error
// const func: <string>(name: never) => void
我们知道这是"Charlie"
,但编译器不知道,所以不能接受它。
另一方面,以下调用被接受:
function bar(x: `Char${string}`) {
func(x); // okay, no way it can be "Andy" or "Bob"
// const func: <`Char${string}`>(name: `Char${string}`) => void
}
模式模板文字类型(如ms/TS#40598中实现的那样)`Char${string}`
表示“以'Char'
开头的任何字符串”。编译器了解到这种类型与Children
之间没有重叠,因此可以工作。
相反,以下调用失败:
function baz(x: `An${string}`) {
func(x); // error
// const func: <`An${string}`>(name: never) => void
}
因为“以'An'
开头的字符串”与Children
之间存在重叠,即"Andy"
。
因此,基本上,在怀疑时,编译器会拒绝调用。这可能会使其有点难以使用,因为不接受任意的string
输入,但这可能是你想要的!
英文:
TypeScript lacks negated types (of the sort implemented in microsoft/TypeScript#29317 but never merged into the language), so there's no specific type corresponding to "all string
s that are not Children
". If there were you could write type NotChildren = string & not Children
and then just have (name: NotChildren) => {}
. Instead, we'll need to simulate them by using generics.
The approach I'll take here is to observe that a generic type T
that's been constrained to string
should be rejected if and only if there's no overlap whatsoever between T
and Children
. That is, the intersection of T
and Children
should be the empty/impossible never
type. So let's make func
generic in T
and give name
the conditional type (T & Children) extends never ? T : never
, which evaluates to T
if T & Children
has no overlap, and never
otherwise:
type Children = "Andy" | "Bob";
export const func = <T extends string>(
name: (T & Children) extends never ? T : never
): void => { };
The basic usage looks like this:
func("Andy"); // error;
// const func: <"Andy">(name: never) => void
func("Bob"); // error
// const func: <"Bob">(name: never) => void
func("Charie"); // okay;
// const func: <"Charie">(name: "Charie") => void
The first two calls fail because when T
is inferred to be the literal types "Andy"
or "Bob"
, then T & Children
is possible, so (T & Children) extends never
is false, and so (T & Children) extends never ? T : never
is never
, and so the arguments are rejected.
The second call succeeds because when T
is inferred to be literal type "Charlie"
, then T & Children
is impossible, so (T & Children) extends never
is true, and so (T & Children) extends never ? T : never
is T
, and so the argument is accepted.
So that looks good, so far.
Now for some more complicated situations:
func(Math.random() < 0.5 ? "Andy" : "Charlie"); // error
// const func: <"Andy" | "Charlie">(name: never) => void
That fails because the argument is either "Andy"
or "Charlie"
, and since there is some overlap between Children
and that type (namely "Andy"
), then it is rejected.
Also rejected is an arbitrary string
:
function foo(x: string) {
func(x); // error, maybe it's "Andy" or "Bob"
// const func: <string>(name: never) => void
}
because there's overlap between string
and Children
(namely, Children
). Note that the compiler and a human being might have a different view of what constitutes an "arbitrary" string. If you use the +
operator to concatenate strings, the compiler doesn't try to figure out what that might be, and just widens it to string
. So this is rejected:
func("Char" + "lie"); // error
// const func: <string>(name: never) => void
We know that's "Charlie"
, but the compiler doesn't, so it can't accept it.
On the other hand it accepts the following call:
function bar(x: `Char${string}`) {
func(x); // okay, no way it can be "Andy" or "Bob"
// const func: <`Char${string}`>(name: `Char${string}`) => void
}
The pattern template literal type (as implemented in ms/TS#40598) `Char${string}`
means "any string starting with "Char"
". The compiler understands that there is no overlap between such a type and Children
, so it works.
Conversely the following fails:
function baz(x: `An${string}`) {
func(x); // error
// const func: <`An${string}`>(name: never) => void
}
because there is overlap between "strings that start with "An"
" and Children
, namely "Andy"
.
So essentially, when in doubt, the compiler rejects the call. That could make it somewhat difficult to use, since arbitrary string
inputs are not accepted, but that's presumably what you want!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论