You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
})

正确的用法有两种:

  1. 明确指定返回的子集类型
db.query<Pick<User, "name">>(
    {text:`SELECT name FROM user LIMIT 1`},
    { returnColumns: ['name']}
).then(result => {
    console.log(result?.name) // 无错误,仅能访问name字段
})
  1. 让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 08:44:58