如果IMyInterface 有其他成员需要保留,我想提出一个更通用的@Catalyst 响应版本。
type EachExpanded<T> = {
[key in keyof T]: { [subKey in key]: T[key]; }
};
type FixedSubset<T, U> = Pick<T, Exclude<keyof T, U>>;
type AtLeastSubset<T, U> = Pick<T, Extract<keyof T, U>>;
type AtLeaseOne<T, U> = FixedSubset<T, U> & EachExpanded<AtLeastSubset<T, U>>[keyof AtLeastSubset<T, U>];
const example1: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
{ a: 3, b: 4, c: '4' } // valid
const example2: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
{ a: 1, c: '1' } // valid
const example3: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
{ b: 2, c: '2' } // valid
const example4: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
{ c: '3' } // invalid
请记住,此响应使用刚刚发布的 TypeScript 2.8 版中引入的 Exclude 和 Extract 关键字。这些是条件类型的一部分。
在代码中,我们假设类型 T 是原始类型或接口,U 是一组键,其中至少一个必须存在。
它的工作方式是通过排除需要基于原始类型T实现的属性来创建一个类型。这是使用 Pick<T, Exclude<keyof T, U>> 定义的,其中包含不在 U 中的所有内容。
然后,创建另一种类型,只包含必须至少存在一个的元素,Pick<T, Extract<keyof T, U>>。
EachExpanded 将每个特殊集合的类型存储在同一键下。例如,如果键 'a' 和 'b' 将成为上述示例的有条件可选,EachExpanded 创建以下类型:
{
a: { a: number; };
b: { b: number; };
}
这将在最终类型中与交集运算符一起使用,因此至少要强制存在其中一个。
基本上,对于上面的示例,我们将得到以下结果:
{ c: string; } & ({ a: number; } | { b: number; })