TypeScript中如何让DB.query泛型与returnColumns参数类型一致?
问题描述
我编写了一个用于生成SQL查询的DB类,相关代码如下:
interface QueryConfig<I extends any[] = any[]> { name?: string | undefined; text: string; values?: I | undefined; } interface QueryOptions<R> { returnColumns?: Array<keyof R>; cacheType?: string; } interface QueryOptionsWithMandatoryReturnColumns<R> extends QueryOptions<R> { returnColumns: Array<keyof R> } class DB { query<R>(SQL: QueryConfig, options: QueryOptionsWithMandatoryReturnColumns<R>): Promise<Pick<R, typeof options['returnColumns'][number]> | null>; query<R>(SQL: QueryConfig): Promise<R | null>; query<R>(SQL: QueryConfig, options?: QueryOptions<R>): Promise<R | null>; async query<R>(SQL: QueryConfig, options?: QueryOptions<R>) { return {name: 'foo'} as R; } }
使用示例:
const db = new DB() class User { name!: number; surname!: number; } db.query<User>({text: `SELECT name FROM user LIMIT 1`}).then(result => { console.log(result?.name, result?.surname) })
我希望当传入returnColumns参数时,TypeScript能检查泛型参数(如query<User>)与returnColumns(keyof User)的一致性。例如执行以下代码时:
db.query<User>( {text:`SELECT name FROM user LIMIT 1`}, { returnColumns: ['name']} );
TypeScript能给出如下错误提示:
db.query<User>( // -----^^^^^^ // ERROR: query return only name of User {text:`SELECT name FROM user LIMIT 1`}, { returnColumns: ['name']} ).then(result => { console.log(result?.name, result?.surname) // ----------------------^^^^^^^^^^^^^^^^ // ERROR: surname is in User but not in returnColumns })
请问是否可以实现这样的类型检查?
解决方案
可以通过拆分泛型参数、调整重载签名的约束逻辑来实现这种类型检查。核心是让TypeScript明确区分“完整实体类型”和“返回列子集类型”,当传入returnColumns时,强制泛型参数必须匹配子集类型,而非完整实体。
修改后的DB类代码如下:
interface QueryConfig<I extends any[] = any[]> { name?: string | undefined; text: string; values?: I | undefined; } // 将返回列单独作为泛型参数,与实体类型解耦 interface QueryOptions<Columns extends string | number | symbol> { returnColumns?: Columns[]; cacheType?: string; } interface QueryOptionsWithMandatoryReturnColumns<Columns extends string | number | symbol> extends QueryOptions<Columns> { returnColumns: Columns[]; } class DB { // 传入returnColumns时:绑定实体类型OriginalR和返回列Columns,返回子集类型Pick<OriginalR, Columns> query<OriginalR, Columns extends keyof OriginalR>( SQL: QueryConfig, options: QueryOptionsWithMandatoryReturnColumns<Columns> ): Promise<Pick<OriginalR, Columns> | null>; // 不传returnColumns时:直接返回完整实体类型OriginalR query<OriginalR>(SQL: QueryConfig): Promise<OriginalR | null>; // 通用重载签名 query<OriginalR, Columns extends keyof OriginalR>( SQL: QueryConfig, options?: QueryOptions<Columns> ): Promise<OriginalR | Pick<OriginalR, Columns> | null>; async query<OriginalR, Columns extends keyof OriginalR>( SQL: QueryConfig, options?: QueryOptions<Columns> ) { return {name: 'foo'} as any; } }
现在执行你期望的错误场景代码时,TypeScript会给出精准提示:
const db = new DB() class User { name!: number; surname!: number; } // 错误:类型“User”不满足类型“Pick<User, "name">” db.query<User>( {text:`SELECT name FROM user LIMIT 1`}, { returnColumns: ['name']} ).then(result => { // 错误:类型“Pick<User, "name">”上不存在属性“surname” console.log(result?.name, result?.surname) })
正确的用法有两种:
- 明确指定返回的子集类型
db.query<Pick<User, "name">>( {text:`SELECT name FROM user LIMIT 1`}, { returnColumns: ['name']} ).then(result => { console.log(result?.name) // 无错误,仅能访问name字段 })
- 让TypeScript自动推断返回类型(需用
as const固定数组元素类型)
db.query( {text:`SELECT name FROM user LIMIT 1`}, { returnColumns: ['name'] as const} ).then(result => { console.log(result?.name) // result被自动推断为Pick<User, "name"> | null })
内容的提问来源于stack exchange,提问作者ar099968
相关产品推荐
相关产品推荐

