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

TypeScript中实现动态懒加载代理集合类的类型提示问题

解决TypeScript中Proxy动态属性类的类型提示问题

问题背景

通过Proxy实现了一个懒加载数据库连接的DatabasesCollection类,在Node环境中运行正常,但TypeScript会提示“属性不存在”的错误。目前使用Record<string, Knex>类型断言作为临时方案,希望找到正确的类型配置方式。

解决方案

核心思路是告诉TypeScript:该类的实例支持任意字符串属性名,且属性值类型为指定的泛型T。可以通过以下几种方式实现:

方案1:给类添加索引签名

直接在DatabasesCollection类中添加字符串索引签名,明确任意字符串属性的返回类型:

const debug = true;
process.env['DB_TEST_URL'] = 'mysql://root@localhost/test';

class NotImportantConnector {
    public url: string;

    constructor(url: string) {
        this.url = url;
    }
}

function dbFromURL(url: string): NotImportantConnector {
    return new NotImportantConnector(url);
}

class DatabasesCollection<T> {
    protected databases: Record<string, T> = {};
    // 添加索引签名:任意字符串属性返回T类型
    [key: string]: T;

    constructor() {
        return new Proxy(this, this) as DatabasesCollection<T>;
    }

    get(target: DatabasesCollection<T>, name: string): T {
        if (name in target.databases) {
            return target.databases[name];
        }

        const envName = `DB_${name.replace(/[A-Z]/g, l => `_${l}`).toUpperCase()}_URL`;

        if (!process.env[envName]) {
            throw new Error(`Call to database ${name} needs ${envName} environment variable to run`);
        }

        target.databases[name] = dbFromURL(process.env[envName] as string) as T;
        return target.databases[name];
    }
}

// 现在可以直接使用,不再报错
const db = new DatabasesCollection<NotImportantConnector>();
console.log(db.test);

方案2:用交叉类型创建实例

如果不想修改类的定义,可以通过工厂函数返回交叉类型,同时保留类的原有类型和动态属性类型:

// 类定义保持不变(去掉索引签名)
class DatabasesCollection<T> {
    protected databases: Record<string, T> = {};

    constructor() {
        return new Proxy(this, this) as any;
    }

    get(target: DatabasesCollection<T>, name: string): T {
        if (name in target.databases) {
            return target.databases[name];
        }

        const envName = `DB_${name.replace(/[A-Z]/g, l => `_${l}`).toUpperCase()}_URL`;

        if (!process.env[envName]) {
            throw new Error(`Call to database ${name} needs ${envName} environment variable to run`);
        }

        target.databases[name] = dbFromURL(process.env[envName] as string) as T;
        return target.databases[name];
    }
}

// 工厂函数返回交叉类型
function createDBCollection<T>(): DatabasesCollection<T> & Record<string, T> {
    return new DatabasesCollection<T>() as DatabasesCollection<T> & Record<string, T>;
}

// 使用工厂函数创建实例
const db = createDBCollection<NotImportantConnector>();
console.log(db.test);

说明

  • 索引签名是TypeScript描述动态属性的标准方式,方案1最直接,让类的类型本身就支持任意字符串属性。
  • 方案2适合不想修改类定义的场景,通过交叉类型合并原有类类型和动态属性类型。
  • 两种方案都避免了使用unknown类型断言的临时方案,类型更安全、准确。

内容的提问来源于stack exchange,提问作者Felipe Buccioni

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 16:42:47