Typescript -从字符串中排除保留字

zazmityj  于 2023-01-31  发布在  TypeScript
关注(0)|答案(1)|浏览(299)

我想定义一个接口,不允许分配'选项'字符串的configNames字段,但无法弄清楚如何使其工作:

// First try
interface Options<T = string> {
    configNames: T extends 'options' ? never: string;
}

// No validation because 'options' is subset of string, thus the type is always false, thus configName type is string
const options: Options = {
    configNames: 'options'
}

// Second try
interface Options<T> {
    configNames: T extends 'options' ? never: string;
}

// typescript error: Generic type 'Options<T>' requires 1 type argument(s).
const options: Options = {
    configNames: 'options'
}

是否有任何变通方案来实现这一点?

goucqfw6

goucqfw61#

TypeScript中没有与“除"options"之外的所有string“对应的特定类型。这将需要类似于microsoft/TypeScript#4196中建议的 negated types 的内容(甚至在从未合并的microsoft/TypeScript#29317中实现)。如果TypeScript对类型取反,那么您大概可以编写类似string & not "options"的代码。但它没有。如果使用特定的Options类型是一个要求,那么您想要的是目前不可能的。
因此,您只剩下一些变通方法:没有特定的类型,但是可以将Options<T>设置为generic类型(如第一次尝试所示),然后将其用作候选类型的 * 约束 *。您可以编写一个帮助函数来为您推断泛型类型参数T。因此,虽然无法编写const o: Options = {...}并使其按预期行为,您 * 可以 * 编写const o = options({...})并获得类似的效果(如果您斜视它们,它们实际上并没有太大的不同)。
我们的想法是使用conditional typesT中的任何字符串类型中过滤掉"options",你可以使用Exclude<T, U>实用程序类型来完成这个任务:

interface Options<T extends string> {
  configNames: Exclude<T, "options">
}

这与您的版本类似。另外,如果您想禁止string本身(这样{configNames: someRandomString}就会被拒绝,因为它可能是"options"),您也可以扩展定义来实现这一点:

interface Options<T extends string> {
  configNames: string extends T ? never : Exclude<T, "options">
}

这取决于你是更关心误报还是漏报。

const options = <T extends string>(o: Options<T>) => o;

现在您可以进行测试:

const bad = options({
  configNames: 'options' // error!
});

const good = options({
  configNames: 'somethingElse' // okay
});
// const good: Options<"somethingElse">

function foo(someRandomString: string) {
  const maybe = options({
    configNames: someRandomString // maybe error depending on needs
  })
}

看起来不错。您得到了您所关心的验证。它与您所期望的方法有些不同,但它有可能实现的好处。
Playground代码链接

相关问题