NodeJS 从对象键检索推断的字符串文本

krugob8w  于 2023-01-30  发布在  Node.js
关注(0)|答案(1)|浏览(141)

我正在尝试创建基于键定义值的类型。如果键扩展$${string}(例如$foo),则值应为不带前缀的键,例如foo。如果键不扩展$${string}(例如boo),则值应为null

    • 示例**
const example = {
  $foo: 'foo',
  boo: null,
}

下面是我创建的一个独立的示例-但是当我将其应用到下面的代码中时,它并没有按预期工作。😕

type Value<T> = T extends `$${infer I}` ? I : null

type ExampleA = Value<'$foo'> // type is 'foo' 
type ExampleB = Value<'boo'>  // type is null
    • 我的当前代码**
type Values = {
    [K in string]: K extends `$${infer p}` ? p : null;
}

const values = {
    $foo: 'foo', // Type 'string' is not assignable to type 'null'.
    foo: null,
    
    $boo: 'boo', // Type 'string' is not assignable to type 'null'.
    boo: null,
} satisfies Values;

type Expected = {
    readonly $foo: 'foo',
    readonly foo: null,
    
    readonly $boo: 'boo',
    readonly boo: null,
}
    • satisfies Values用于稍后推断类型。类似的方法是可接受的🙂

谢谢你的帮助和时间-干杯

jjjwad0x

jjjwad0x1#

Values类型的问题在于string上的mapped types没有按照您期望的方式工作。虽然 * 概念上 * 您可以将string视为所有可能的字符串类型的无限并集,但string上的Map类型甚至不会尝试迭代所有可能的字符串类型;它只Map了一件事string

type Values = {
  [K in string]: K extends `\$${infer S}` ? S : null;
}
/* type Values = {
    [x: string]: null;
} */

由于string不扩展\$${infer S},因此string键的属性类型为null
正如microsoft/TypeScript#22509中所讨论的,这是按预期工作的。string上的Map类型不是您想要的。
不幸的是,没有办法在TypeScript中编写一个特定的类型,使其按照您想要的方式工作。

type Values = {
  [k: `\$${string}`]: string;
  [k: string]: string | null;
}

使用模板字符串模式索引签名,但无法表示属性值字符串需要匹配"$"字符后的部分(不仅仅是string)和其他键需要具有null的部分(不仅仅是string | null):

const values = {
  $foo: 'foo',
  foo: null,
  $boo: 'boo',
  boo: null,
  $oops: null, // error, not string
  oops: 'hmm', // should be error, but isn't!
  $whoops: 'oops', // should be error, but isn't!
} satisfies Values;

因此,我们不得不放弃使用satisfies操作符的方法,因为没有合适的Values类型可以使用它。
你真正关心的是让编译器推断出values的类型,但仍然检查你想要的约束。我们可以通过用generic帮助函数替换satisfies Values来获得这样的行为,我们可以调用satisfiesValues()。在运行时,这个函数只返回它的输入,但是编译器可以使用它来验证传入的对象字面值。因此,您可以编写const values = satisfiesValues({...});而不是const values = {...} satisfies Values;
下面是一个可能的实现:

const satisfiesValues = <K extends PropertyKey>(
  val: { [P in K]: P extends `\$${infer S}` ? S : null }
) => val;

该函数在K中是泛型的,val是传入的val值的键。这很可能是已知键的某种并集(没有一个键只是string),然后Map的类型按预期的方式运行:

const values = satisfiesValues({
  $foo: 'foo',
  foo: null,
  $boo: 'boo',
  boo: null,
  $oops: null, // error, not "oops"
  oops: 'hmm', // error, not null
  $whoops: 'oops', // error, not "whoops"
});

/* const values: {
    foo: null;
    $foo: "foo";
    boo: null;
    $boo: "boo";
    oops: null;
    $whoops: "whoops";
    $oops: "oops";
} */

看起来不错。values的类型是你想要的,编译器允许有效的属性,并抱怨无效的属性。
Playground代码链接

相关问题