TypeScript 映射类型,有条件地添加可选修饰符

5

能否有条件地使映射类型属性变为可选?

考虑以下类型:

type Definition {
  name: string,
  defaultImplementation?: ImplementationType
}

并记录它们:

type DefinitionMap = Record<string, Definition>

我愿意创建一个映射类型,如果输入提供了,则其实现是可选的,但如果没有提供,则该映射类型的实现是必需的。
对于像这样的DefinitionMap
{
  foo: { name: 'x' },
  bar: { name: 'y', defaultImplementation: { /*...*/ } }
}

我希望有一个像这样的映射类型

{
  foo: ImplementationType,
  bar?: ImplementationType
}

我一直在尝试使用条件语句并添加 undefined 类型,但这似乎不起作用。

type ImplementationMap<T extends DefinitionMap> = {
  [K in keyof T]: T[K] extends { defaultImplementation: any }
    ? ImplementationType | undefined
    : ImplementationType
}

我知道条件分支的行为符合我的预期,但是添加 undefined 实际上并没有使该字段变为可选。

2个回答

7

我假设 DefinitionMap 应该是 Record<string,Definition>(而不是 Record<string,A>)。

尝试这样做:

// Gets the keys of T whose values are assignable to V
type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T]

type ImplementationMap<T extends DefinitionMap> =
    // A partial (all properties are optional) record for all the keys
    Partial<Record<keyof T, ImplementationType>> &
    // Require ImplementationType for all the keys that do not have defaultImplementation
    Record<KeysMatching<T, { defaultImplementation?: undefined }>, ImplementationType>

/*
Test is equivalent to
{
  foo: ImplementationType,
  bar?: ImplementationType,
  baz: ImplementationType
}
*/
type Test = ImplementationMap<{
  foo: { name: 'x' },
  bar: { name: 'y', defaultImplementation: { /*...*/ } },
  baz: { name: 'z', defaultImplementaiton: undefined }
}>

1
我喜欢这个答案比@kaya3的更好。更加直接明了。 - gatsbyz
1
这样读起来更好。但是我的真实类型本身就是有趣的泛型,所以@kaya3的答案更加通用。它们基本上是相同的,只是使用Partial作为快捷方式。 - ptpaterson
这个实用工具本身非常有用:type KeysMatching<T,V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T]。它允许进行大量高级魔法。正是我所需要的! - Bence Szalai

4
这是一个解决方案:
type NonImplementedKeys<T extends DefinitionMap> = {[K in keyof T]: T[K] extends {defaultImplementation: ImplementationType} ? never : K}[keyof T]
type NiceIntersection<S, T> = {[K in keyof (S & T)]: (S & T)[K]}
type ImplementationMap<T extends DefinitionMap> = NiceIntersection<{
    [K in NonImplementedKeys<T>]: ImplementationType
}, {
    [K in keyof T]?: ImplementationType
}>

例子:

type DefinitionMapExample = {
  foo: { name: 'x' },
  bar: { name: 'y', defaultImplementation: { /*...*/ } }
}

// {foo: ImplementationType, bar?: ImplementationType | undefined}
type ImplementationMapExample = ImplementationMap<DefinitionMapExample>
< p> NiceIntersection<S,T> 类型相当于一个普通的交集类型 S & T,但它使结果看起来像这样 {foo:...,bar?:...},而不是 {foo:...} & {bar?:...}。 < / p>

游乐场链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接