我想创建一个 Typescript 实用程序类型,如果对象包含与模板匹配的键,则它可以从复杂的对象联合中提取某些子类型。为了简单起见,假设我想匹配包含以下划线开头的任何键的对象类型。
但是,带有模板文字的键的条件类型似乎总是匹配任何对象,无论是否存在匹配的键。这是一个简化的示例:
// ❌ Conditional type on object with template literal key not working as expected
type HasTemplateProperty<T> = T extends { [key: `_${string}`]: string } ? true : false
type testA1 = HasTemplateProperty<{ _test: string }> // ✅ true, as expected
type testA2 = HasTemplateProperty<{ test: string }> // ❌ true, not expected
type testA3 = HasTemplateProperty<{}> // ❌ true, not expected
type testA4 = HasTemplateProperty<number> // ✅ false, as expected
// ✅ Conditional type on object with string literal key is working as expected
type HasLiteralProperty<T> = T extends { _test: string } ? true : false
type testB1 = HasLiteralProperty<{ _test: string }> // ✅ true, as expected
type testB2 = HasLiteralProperty<{ test: string }> // ✅ false, as expected
type testB3 = HasLiteralProperty<{}> // ✅ false, as expected
type testB4 = HasLiteralProperty<number> // ✅ false, as expected
// ✅ Conditional type template literal on its own is working as expected
type MatchesTemplate<T> = T extends `_${string}` ? true : false
type testC1 = MatchesTemplate<'_test'> // ✅ true, as expected
type testC2 = MatchesTemplate<'test'> // ✅ false, as expected
type testC3 = MatchesTemplate<'test_'> // ✅ false, as expected
type testC4 = MatchesTemplate<number> // ✅ false, as expected
我已经看到了有关 Typescript 模板文本和对象键中意外行为的一些其他讨论,但我找不到有关此特定行为的任何信息。我还在 Typescript 的 GitHub 上发现了这个未解决的问题,其中讨论了计算属性键名称意外地扩展为
string
,这可能是相关的,但没有解释“testA3”,其中没有属性的对象被视为匹配。
为什么 Typescript 会这样做,有没有办法获得 Typescript 实用程序类型的预期行为,该类型匹配包含与
{ _anything: '...', anythingElse: 123 }
等模板匹配的键的对象,但不匹配不包含 { anything: '...', anythingElse: 123 }
等键的对象?
(我认为映射属性在我的情况下不起作用,因为我想在对象级别而不是单个属性级别获得匹配)
您在条件类型中描述的是索引访问类型。所以它可以包含键
_${string}
的属性,也可以不包含。这就是为什么 {}
给出 true
。由于 {test: string}
在结构上满足 {}
,因此也给出 true
:
type Check = { test: string } extends {} ? true : false; // true
这是因为
{ test: string }
可以用作TS中的{}
。
为了使您的条件类型起作用,您可以检查实际的属性键和值是否满足您的条件: 游乐场
type HasTemplateProperty<T> = keyof T extends `_${string}` ? T[keyof T] extends string ? true : false : false;
type testA1 = HasTemplateProperty<{ _test: string }> // ✅ true, as expected
type testA2 = HasTemplateProperty<{ test: string }> // ✅ false, as expected