Typescript:如何从具有映射/记录类型值的类属性返回特定类型?

标签 typescript class dictionary generics record

我有以下类(class)DataStore具有 Map 类型的两个属性。我正在尝试想出一个方法getEntity它将根据参数返回正确的数据类型。还尝试创建一个方法updateEntity用于更新属性。

type TUUID = string;

interface IUser {
    name: string;
    type: 'customer' | 'client';
}

interface IProduct {
    name: string;
    count: number;
    type: 'electronics' | 'food' | 'drinks';
}

const _user: IUser = { name: 'John Doe', type: 'customer' };

interface IDataStore {
    users: Map<TUUID, IUser>;
    products: Map<TUUID, IProduct>;
}

export class DataStore implements IDataStore {
    users: IDataStore['users'] = new Map();
    products: IDataStore['products'] = new Map();

    getEntity<T extends keyof IDataStore>(storeKey: T, id: TUUID) {
        if (this[storeKey] && this[storeKey].has(id)) return this[storeKey].get(id);
        return null;
    }

    updateEntity<T extends keyof IDataStore>(storeKey: T, id: TUUID, data: ReturnType<IDataStore[T]['get']>) {
        if (this[storeKey]) {
            this[storeKey].set(id, data);
            return this[storeKey].get(id);
        } else {
            return null;
        }
    }
}

const store = new DataStore();
const user = store.getEntity('users', '1234');
const product = store.getEntity('products', 'abcd');
const updatedUser = store.updateEntity('users', '1234', _user);

但是 typescript 推断出 getEntity 的数据类型如IUser | IProduct | null | undefined 。当尝试访问product.count时,它提供以下错误:

Property 'count' does not exist on type 'IUser | IProduct'.
  Property 'count' does not exist on type 'IUser'.

此外,这一行 this[storeKey].set(id, data);提供错误:

(parameter) data: IUser | IProduct | undefined
Argument of type 'IUser | IProduct | undefined' is not assignable to parameter of type 'never'.
  The intersection 'IUser & IProduct' was reduced to 'never' because property 'type' has conflicting types in some constituents.
    Type 'undefined' is not assignable to type 'never'.ts(2345)

如果我进行方法重载,它基本上可以工作。但随后方法内部的方法参数就变成any这很容易出错。我尝试使用 Record 并且只是纯对象,仍然有同样的问题。但是,如果我也为特定属性执行方法,如 getUser , getProduct , updateUserupdateProduct它确实推断出了正确的类型。

有没有办法拥有一个能够正确推断数据类型的通用方法?

最佳答案

首先,为了this[storeKey].set(id, data)要工作,您需要重构 this 的类型和data为了让编译器将它们视为明显彼此兼容。类似 this[storeKey].set() 的一般问题和data关联union types microsoft/TypeScript#30581 中描述,以及 microsoft/TypeScript#47109 中实现和描述的修复是重构为分布式对象类型

例如,这里有一种重写 IDataStore 的方法:

interface DataMap {
  users: IUser;
  products: IProduct;
}

type IDataStore = { [K in keyof DataMap]: Map<TUUID, DataMap[K]> }

看起来像一个无操作,事实上,如果你检查IDataStore ,看起来和以前一样:

/*type IDataStore = {
    users: Map<TUUID, IUser>;
    products: Map<TUUID, IProduct>;
}*/

但这里重要的是编译器知道 IDataStore一般作用于 K extends keyof DataMap以某种方式它不知道你原来的定义。在 updateEntity我们还更改 data 的类型至DataMap[K]而不是ReturnType<IDataStore[K]['get']> 。前者被视为与 IDataStore 有关。通用定义 K ,而后者太复杂并且使用 the ReturnType<T> utility type它被实现为 conditional type .

这就是我们到目前为止所得到的:

getEntity<K extends keyof IDataStore>(storeKey: K, id: TUUID) {
  return this[storeKey].get(id) ?? null;
} // returns IUser | IProduct | null

updateEntity<K extends keyof IDataStore>(storeKey: K, id: TUUID, data: DataMap[K]) {
  this[storeKey].set(id, data); // error! Argument of type 
  // 'IUser | IProduct' is not assignable to parameter of type 'never'
  return data;
}

(请注意,我删除了您的存在/不存在测试,因为您的属性不是可选的)。这显然不是一个进步; getEntity() 的输出类型仍然是一个联盟,并且 set()方法仍然提示交集。现在的问题是 this 的类型是polymorphic this type编译器将其视为隐式 generic类型参数。它自动表示实现子类的类型。这会让编译器感到困惑,它无法看到与 IDataStore 的连接。 。编译器 View this[storeKey]作为联合类型,因为它只是加宽 this到其DataStore类(Class)约束。我们希望按照 IDataStore 开展工作这是专门为维护相关性而构建的。

我在这里的方法是安全地扩大 thisIDataStore在访问storeKey之前属性:

getEntity<K extends keyof IDataStore>(storeKey: K, id: TUUID) {
  const thiz: IDataStore = this;
  return thiz[storeKey].get(id) ?? null;
} // returns NonNullable<DataMap[K]> | null

updateEntity<K extends keyof IDataStore>(storeKey: K, id: TUUID, data: DataMap[K]) {
  const thiz: IDataStore = this;
  thiz[storeKey].set(id, data); // okay
} 

现在一切正常了。 thiz[storeKey]的类型现在以通用 K 表示输入参数,这样 get()返回DataMap[K] | undefinedset()接受DataMap[K] .

Playground link to code

关于Typescript:如何从具有映射/记录类型值的类属性返回特定类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72587596/

相关文章:

typescript - vscode typescript : 'Add all missing imports' shortcut

c++ - 两个数组下标重载读写

java - 如何从属于它的实例变量之一获取类的特定实例

swift - 比嵌套字典更合适的解决方案?

iphone - 如何避免调用位置更新了三次

javascript - 如何确定 Google Maps loadGeoJson 何时完成?

javascript - 根据字段从数组中获取javascript对象

java - 构造函数中带有 super 的 UML 类图

python - 如何检查并引发没有命令行参数的错误

typescript - 当启用标志 --strictNullChecks 时,null 不可分配给 void