TypeScript中让Schema可选的惯用泛型实现方式探讨
背景
我开发了一个库,允许用户定义Schema,并根据定义返回自定义响应类型,以下是最简示例:
interface Item { id: string; } interface CardinalityDef { [objectType: string]: 'one' | 'many' } class Schema<Cardinality extends CardinalityDef> { constructor(public cardinality: Cardinality) {} } type ResponseOf< S extends Schema<any>, K extends keyof S['cardinality'] > = S['cardinality'][K] extends 'one' ? Item : Item[]; class DB<S extends Schema<any>>{ query<ObjectType extends keyof S['cardinality']>(objectType: ObjectType): ResponseOf<S, ObjectType> { return 1 as any; } } function init<S extends Schema<any>>(schema: S): DB<S> { return 1 as any; }
现在可以定义Schema并得到预期的响应类型:
const schema = new Schema({posts: 'many', author: 'one'}) const db = init(schema); // 类型正确推导为 Item[] const postsResponse = db.query('posts'); // 类型正确推导为 Item const authorResponse = db.query('author');
目标:让Schema可选
希望用户无需提供Schema即可使用:
const db = init(); // 不传入schema
若未提供Schema,所有查询响应应返回Item[]:
const postsResponse: Item[] = db.query('posts'); const authorResponse: Item[] = db.query('author');
方案1:IsAny类型检查
将schema参数设为可选,更新ResponseOf类型:
function init<S extends Schema<any>>(schema?: S): DB<S> { return 1 as any; }
type IsAny<T> = 0 extends (1 & T) ? true : never; type ResponseOf< S extends Schema<any>, K extends keyof S['cardinality'] > = IsAny<S['cardinality']> extends true ? Item[] : S['cardinality'][K] extends 'one' ? Item : Item[];
此方案可正常工作,但存在疑问:
- db被类型化为
Schema<any>,但实际无Schema,不符合预期 - 需在ResponseOf中显式检查any,感觉违背TS设计思路
方案2:Schema | undefined
显式处理undefined情况:
function init<S extends Schema<any> | undefined>(schema?: S): DB<S> { return 1 as any; }
该方案也存在缺点:
- 需在整个系统中传递
| undefined类型 - 未提供Schema时,TS未将S细化为严格的undefined
问题与讨论
哪种方案更符合TypeScript惯用风格?是否有其他实现可选Schema的更好方法?我倾向于默认Schema类型并显式IsAny检查,理由是无需传递undefined类型且能细化类型,欢迎各位建议。
内容的提问来源于stack exchange,提问作者Stepan Parunashvili
相关产品推荐
相关产品推荐

