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

TypeScript:能否合并泛型ObservableObject与T并基于keyof识别字段类型?

问题解答

1. 直接访问ObservableObject的成员(无需调用get)

可以通过类型层面的交叉类型+运行时代理/动态挂载实现,两种方案如下:

方案一:Proxy代理拦截属性读写

用Proxy包装实例,自动映射get/set方法;类型上让实例同时具备ObservableObject<T>和T的特性:

class ObservableObject<T> {
    private _data: T;
    constructor(value: T) {
        this._data = { ...value };
    }
    get<K extends keyof T>(name: K): T[K] {
        return this._data[name];
    }
    set<K extends keyof T>(name: K, value: T[K]): void {
        this._data[name] = value;
        // 此处可添加Observable的状态通知逻辑
    }
}

// 包装工具函数
function createObservable<T>(value: T): ObservableObject<T> & T {
    const observable = new ObservableObject(value);
    return new Proxy(observable as ObservableObject<T> & T, {
        get(target, prop: keyof T) {
            if (typeof prop === 'string' && prop in target._data) {
                return target.get(prop);
            }
            return (target as any)[prop];
        },
        set(target, prop: keyof T, value) {
            if (typeof prop === 'string' && prop in target._data) {
                target.set(prop, value);
                return true;
            }
            (target as any)[prop] = value;
            return true;
        }
    });
}

// 使用示例
interface Dog { name: string; age: number }
let observableDog = createObservable<Dog>({ name: "Fifi", age: 1 });

// 直接访问属性
console.log(observableDog.name); // "Fifi"
observableDog.age = 2;
console.log(observableDog.get("age")); // 2

方案二:构造函数动态挂载属性

在实例初始化时,把源对象的属性直接挂载到实例上,绑定到get/set方法:

class ObservableObject<T> {
    private _data: T;
    constructor(value: T) {
        this._data = { ...value };
        // 动态生成属性访问器
        Object.keys(value).forEach(key => {
            Object.defineProperty(this, key, {
                get: () => this.get(key as keyof T),
                set: (val) => this.set(key as keyof T, val),
                enumerable: true
            });
        });
    }
    get<K extends keyof T>(name: K): T[K] {
        return this._data[name];
    }
    set<K extends keyof T>(name: K, value: T[K]): void {
        this._data[name] = value;
        // 状态通知逻辑
    }
}

// 使用示例
interface Dog { name: string; age: number }
let observableDog = new ObservableObject<Dog>({ name: "Fifi", age: 1 });

console.log(observableDog.name); // "Fifi"
observableDog.name = "Max";
console.log(observableDog.get("name")); // "Max"

2. TypeScript基于keyof识别字段类型

完全可以,只需给set方法添加泛型类型约束,让value的类型严格匹配目标字段的类型:

修改ObservableObject的方法定义:

class ObservableObject<T> {
    constructor(value: T);
    get<K extends keyof T>(name: K): T[K];
    // 用泛型K约束value为对应字段的类型
    set<K extends keyof T>(name: K, value: T[K]): void;
}

此时传入错误类型会直接触发TypeScript报错:

interface Dog { name: string; age: number }
let observableDog = new ObservableObject<Dog>({ name: "Fifi", age: 1 });

observableDog.set("name", 0); // 类型错误:不能将类型“number”分配给类型“string”

原定义中set方法的value未指定类型,导致TypeScript无法校验匹配关系,添加泛型约束后即可实现精确的类型检查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:55:24