【问题标题】:Typescript generics don't resolve concrete types as I would like them to beTypescript 泛型不会像我希望的那样解析具体类型
【发布时间】:2021-04-20 09:19:33
【问题描述】:

我有一个保存项目列表的类,但不是将它们保存在一个平面数组中,而是将它们保存在一个对象映射中,其中每个属性都代表一个项目组。就像我们有一个汽车清单,然后按制造商对它们进行分组。

// object map
interface IObjectMap<TValue> {
    [key: string]: TValue;
}

// object map; same as interface but build using keys union type
type ItemMap<TMapKeys extends keyof IObjectMap<unknown>, TValue> = Record<TMapKeys, TValue[]>;

// just a function taking one parameter and returning a result
type GetterFunc<TInput, TResult> = (item: TInput) => TResult;

// object map where the value type of properties are getter functions returning a string
type GetterMap<TMapKeys extends keyof IObjectMap<unknown>, TInput> = Record<TMapKeys, GetterFunc<TInput, string>>;

class GroupedItems<
    TItem, // item type
    TGroupKeys extends keyof IObjectMap<unknown> // object map keys
> {
    public groups: ItemMap<TGroupKeys, TItem> = {} as ItemMap<TGroupKeys, TItem>;
    public countryGetters: GetterMap<TGroupKeys, TItem> = {} as GetterMap<TGroupKeys, TItem>;

    public addItems(items: TItem[], getGroupKey: GetterFunc<TItem, TGroupKeys>): void {
        this.items.concat(items);
        this.items
            .forEach(item => {
                let name = getGroupKey(item);
                if (this.groups[name] === undefined) {
                    // create placeholder for items
                    this.groups[name] = [];
                }
                // Put the item in the group
                this.groups[name].push(item);
            });
    }

    public assignGetters(getters: GetterMap<TGroupKeys, TItem>) {
        this.countryGetters= getters;
    }
}

类有两个泛型类型参数:

  • 我们将分组的项目类型(即Car
  • 组键(即'renault' | 'peugeot' | ...

然后类成员都被定义为对象映射:

  • groups 将项目保存在组数组中
  • countryGetters 也是一个对象映射,具有与项目组相同的属性,但它们定义了一个函数,返回传入的汽车的制造商国家

使用示例

上面的代码似乎没有错误,但使用它时,类型似乎没有得到应有的解析。当我尝试使用未在映射或联合类型的组键中定义的项目组名称时,我希望编译器/linter 抱怨...

interface Car {
    model: string;
    year: number;
}

interface CarMakers<TValue> extends IObjectMap<TValue> {
    renault: TValue;
    peugeot: TValue;
}

let select = new GroupedItems<
    Car,
    keyof CarMakers<unknown>
>();

select.addItems([
        { model: 'R5', year: 1980, dummy: false }, // error; correct
        { model: '206', year: 2004 },
        { model: '3008', year: 2010 }
    ],
    car =>
        car.year < 2000
        ? 'audi' // should be an error; "audi" not in "keyof MakerGroups<>"
        : 'peugeot'
);
select.assignGetters({
    renault: () => 'France',
    audi: () => 'Germany' // should be an error; "audi" not in "keyof MakerGroups<>"
});

如您所见,Typescript 没有解析组名,因此我的类型定义不够严格(我想),因此我可以无效地操作不存在的组。从编译时的角度来看,上面的代码似乎很好,但应该为我做检查,intellisense 应该帮助我填写组名。

这里是 playground link 要修补。

【问题讨论】:

  • 您的IObjectMap 有一个字符串索引,这意味着它应该包含任何字符串的值。 CarMakers 扩展 IObjectMap 所以 keyof CarMakers&lt;unknown&gt; 是每个字符串,而不仅仅是对象的特定键。
  • 事实证明这是一个简单的解决方法 :) 我正在输入答案。

标签: typescript generics keyof


【解决方案1】:

如果您检查您创建的select 的类型,则它是GroupedItems&lt;Car, string | number&gt;。所以我们可以看到问题是TGroupKeys。我们希望它是'renault' | 'peugeot',而不是string | number

您的接口IObjectMap 有一个index signature,这意味着它应该包含任何字符串的值。 CarMakers 扩展 IObjectMap 所以 keyof CarMakers&lt;unknown&gt; 是每个字符串(和数字也是),而不仅仅是对象的特定键。对于扩展 IObjectMap 的每个对象都是如此。

根据typescript docs

如果你有一个带有字符串索引签名的类型,keyof T 将是 string |数字(不仅仅是字符串,因为在 JavaScript 中,您可以使用字符串 (object["42"]) 或数字 (object[42]))来访问对象属性。

我们需要删除那个索引签名。事实上,我们可以在任何地方删除它。

而不是扩展 keyof IObjectMap&lt;unknown&gt;TMapKeysTGroupKeys 应该扩展 stringPropertyKey -- 一个内置类型,它是所有有效属性键 (string | number | symbol) 的联合。

通过这些更改,我们现在得到了所需的错误:

Type '"audi"' is not assignable to type '"renault" | "peugeot"'
Object literal may only specify known properties, and 'audi' does not exist in type 'Record<"renault" | "peugeot", GetterFunc<Car, string>>'

Playground Link

【讨论】:

    猜你喜欢
    • 2013-06-14
    • 2017-07-05
    • 2019-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多