typescript 键入“除...之外的所有可能的字符串值”

anhgbhbe  于 2023-04-07  发布在  TypeScript
关注(0)|答案(4)|浏览(139)

有没有可能定义一个类型,除了少数指定的字符串值之外,它可以赋值给每个字符串值?我想沿着这个(非编译)例子的思路表达一些东西:

type ReservedNames = "this" | "that"
type FooName = string - ReservedNames;
const f1 : FooName = "This" // Works
const f2 : FooName = "this" // Should error
628mspwn

628mspwn1#

对于这个问题没有一个通用的解决方案,因为在类型脚本类型系统中没有办法表达字符串可以是除了列表之外的任何值的事实。(人们可能认为条件类型Exclude<string, ReservedNames>可以工作,但它不工作,它只是返回到字符串)。
作为一种解决方案,如果我们有一个函数,并且我们特别希望不允许某些常量传入,我们可以使用一个条件类型来检查ReservedNames,如果传入的参数是ReservedNames,那么输入参数的方式实际上是不可能满足的(使用交集类型)。

type ReservedNames = "this" | "that"
type FooName = Exclude<string, ReservedNames>;
const f1 : FooName = "This" // Works
const f2 : FooName = "this" // One might expect this to work but IT DOES NOT as FooName is just evaluates to string

function withName<T extends string>(v: T & (T extends ReservedNames ? "Value is reserved!": {})) {
  return v;
}

withName("this"); // Type '"this"' is not assignable to type '"Value is reserved!"'.
withName("This") // ok

Playground

6l7fqoea

6l7fqoea2#

这在TypeScript中目前是不可能的,但是如果你添加具体的字符串值作为FooName的参数,你可以创建一个可以处理许多实际用例的泛型类型。

type ReservedNames = "this" | "that"
type NotA<T> = T extends ReservedNames ? never : T
type NotB<T> = ReservedNames extends T ? never : T
type FooName<T> = NotA<T> & NotB<T>

const f1: FooName<'This'> = 'This' // works
const f2: FooName<'this'> = 'this' // error

const f3: FooName<string> = 'this' //error
const f4: FooName<any> = 'this' // error
const f5: FooName<unknown> = 'this' // error

在一个函数中,如果你让函数在字符串值上泛型化,它会像预期的那样工作:

function foo<T extends string> (v: FooName<T>) {
  ...
}

foo('this') // error
foo('This') // works
busg9geu

busg9geu3#

警告:
我希望这是一些react组件,我想采取一些字符串值的prop键,应该是任何东西,而不是一组本地化系统查找常量,而accepted answer from ccarton above做的技巧(在这里也适用:Playgrounds),同样值得一提的是,当你的代码不满足类型约束时,typescript的错误消息是 * 完全垃圾 *,并且具有积极的误导性-来自下面的playground链接/代码的示例:

<DemandsNonLocKeys title={"illegal"} text={"!"}/>; // fails, as wanted 🤠

将鼠标悬停在标记为非法的title属性的波浪线上(是的,不是text),您当前(* 使用typescript 4.7.2,以防它在某些方面有所改进)可以看到:

(property) title: "!"
Type '"illegal"' is not assignable to type '"!"'.(2322)

因此,虽然这种方法对于防止提交钩子提交错误代码等事情非常出色,但它依赖于开发人员对这些错误发生时的错误有部落知识,因为错误消息完全脱离了轨道。
完整的示例代码,以防Playgrounds死亡:

import * as React from "react";

const translations = {
    "illegal": "otillåten",
    "forbidden": "förbjuden"
} as const;

type LocDict = typeof translations;
type LocKey = keyof LocDict;
type LocString = LocDict[LocKey]; // stricter constraint than NotLocKey

type NotA<T> = T extends LocKey ? never : T;
type NotB<T> = LocKey extends T ? never : T;
export type NotLocKey<T> = NotA<T> & NotB<T>;

function DemandsNonLocKeys<T extends string>({ title, text }: {
    title: NotLocKey<T>,
    text?: NotLocKey<T>
}) {
    return <>{text}: {title}</>;
};

<DemandsNonLocKeys title={"illegal"} text={"!"}/>;     // fails, as wanted 🤠
<DemandsNonLocKeys title={"not"} text={"forbidden"}/>; // fails, as wanted 🤠
<DemandsNonLocKeys title={"anything"} text={"goes"}/>; // all non-LocKey: ok!
qlvxas9a

qlvxas9a4#

我用以下方法解决了这个问题,其中:
1.创建一个键为Literals的对象,Literals是文字的并集。
1.从对象中省略ExcludedLiterals类型(Literals的子集)中的任何文本。
1.返回对象的keyof分辨率。

type OmitLiteral<Literals extends string | number, ExcludedLiterals extends Literals> = keyof Omit<{ [Key in Literals]: never }, ExcludedLiterals>;

有点复杂,但很有效。

type BaseUnion = "abc" | "def" | "ghi"; // "abc" | "def" | "ghi"
type OmittedUnion = OmitLiteral<BaseUnion, "ghi">; // "abc" | "def"

相关问题