首页 > 解决方案 > TS 条件类型仅从联合中扩展一项

问题描述

我希望创建一个函数,它接受来自接口的键作为输入,Foo仅限于可接受分配的键Foostring

interface Foo {
  a: string;
  b?: string;
  c?: string | boolean;
}

test("a");
test("b"); // Argument of type '"b"' is not assignable to parameter of type 'never'.ts(2345)
test("c"); // Argument of type '"c"' is not assignable to parameter of type 'never'.ts(2345)

function test<K extends keyof Foo>(key: Foo[K] extends string ? K : never) {}

上述(严格模式开启)失败b并且c- 有没有一种方法可以使用extends string(或类似的)而不必完全匹配属性的类型?我对其他潜在类型不感兴趣,只是该类型可能string

标签: typescript

解决方案


因此,您显然正在寻找的测试是string extends Foo[K]而不是Foo[K] extends string

function test<K extends keyof Foo>(key: string extends Foo[K] ? K : never) {}

test("a"); // okay
test("b"); // okay
test("c"); // okay

如果您考虑可分配性,T extends U则意味着T可以将 type 的值分配给 type 的变量U。如果您将“可以接受赋值的属性Foostring翻译成“可以将类型的值分配给按键索引string的属性”,您会得到。在这种情况下,如果您有一个type变量、一个 type 变量和一个type变量,您应该能够编写并编译它。FooKstring extends Foo[K]fooFookKsstringfoo[k] = s

测试Foo[K] extends string将是您是否正在寻找Foo可以将其分配类型变量的属性,这string恰恰相反:您可以编写s = foo[k].


这也正确地拒绝了非string属性:

interface Foo {
  d: number;
}

test("d"); // error!
// Argument of type '"d"' is not assignable to parameter of type 'never'.

它拒绝比更窄string的类型的属性:

interface Foo {
  e: "specificString";
}

test("e"); // error!
// Argument of type '"e"' is not assignable to parameter of type 'never'.

你不能foo.e = s为任何旧字符串写s,所以这就是拒绝的原因。(如果你需要接受这个,那么你要求表达更复杂的东西,而且也不清楚为什么需要那个......因为string在这种情况下你既不能读取也不能写入值)


另请注意,test这似乎不需要是通用的......您可以制作一个通用类型别名,例如:

type KeysAssignableFrom<T, V> = {
  [K in keyof T]-?: V extends T[K] ? K : never
}[keyof T];

它将采用类型TV返回T其属性可从 分配的键V。然后test()变成:

function testConcrete(key: KeysAssignableFrom<Foo, string>) {}

这相当于:

// function testConcrete(key: "a" | "b" | "c"): void

好的,希望有帮助;祝你好运!

链接到代码


推荐阅读