TypeScript中如何将类型参数约束为另一类型的子集?
问题描述
我定义了表示应用所有服务的类型:
type Services = { service0: () => string; service1: () => string; }
想要编写一个函数,仅接受Services的子集类型——也就是只能包含Services里的0个或多个字段,不能有额外字段。比如以下类型是符合要求的:
type Subset = { service0: () => string; }
我尝试用Partial<Services>实现:
const someFn = <S extends Partial<Services>>(subset: S) => { console.log("some function"); }
这种写法在合法场景下能正常工作,但问题是它会接受带额外字段的类型,比如:
type Subset = { service0: () => string; lofasz: number; } someFn<Subset>({ service0: () => "foo", lofasz: 1 })
这种不符合要求的调用也能通过类型检查,请问怎么约束函数参数,让它只接受和Services有共同字段的类型?
解决方案
可以通过以下几种方式实现严格约束:
方法1:用Pick限定字段范围
让泛型参数限定为Services的键名集合,再用Pick生成合法的子集类型,这是最直观的方案:
type Services = { service0: () => string; service1: () => string; } const someFn = <K extends keyof Services>(subset: Pick<Services, K>) => { console.log("some function"); }
调用示例
- 合法调用:
someFn({ service0: () => "foo" }); someFn({ service1: () => "bar" }); someFn({}); // 空对象也允许 - 非法调用(会触发类型报错):
someFn({ service0: () => "foo", lofasz: 1 });
如果要显式指定泛型参数,也只能传入Services的键名:
someFn<'service0'>({ service0: () => "foo" }); // 合法 // someFn<'service0' | 'lofasz'>({ ... }); // 报错,'lofasz'不是Services的键
方法2:通过类型约束排除额外属性
如果希望保留泛型参数为子集类型本身,可以约束子集的所有键都必须是Services的键:
const someFn = <S extends { [K in keyof S]: K extends keyof Services ? Services[K] : never }>(subset: S) => { console.log("some function"); }
这种写法会对额外字段的类型标记为never,从而触发类型错误。
方法3:定义严格子集类型
先定义一个严格的子集类型,直接排除所有不属于Services的字段,再用这个类型约束函数参数:
type StrictSubset<T> = Partial<T> & { [K: string]: never }; const someFn = (subset: StrictSubset<Services>) => { console.log("some function"); }
这种方式不需要泛型,直接限制参数只能包含Services的字段,不能有额外属性。
内容的提问来源于stack exchange,提问作者Adam A
相关产品推荐
相关产品推荐

