Typescript,如何在将项目添加到Map的两个类之间共享绑定的实用程序函数

假设我有两个类,我希望它们在它们之间共享实用程序功能。该实用程序类访问作为对象类型的Map的属性。映射中存储的对象类型对于每个类都是不同的。该函数被称为与调用该函数的类的实例绑定的对象,并作为参数将其应添加到映射中的对象作为参数传递。过度简化的版本可能看起来像这样。

interface fooItem {
    name: string
}

interface barItem {
    name: string
    compact: boolean
}

function setItems(this: Foo | Bar,item: fooItem | barItem) {
    // Error here on 'item' arg
    this.items.set(item.name,item); 
}

class Foo {
    constructor() {
        this.items = new Map();
    }
    items: Map<string,fooItem>;
    setItems = setItems.bind(this);
}

class Bar {
    constructor() {
        this.items = new Map();
    }
    items: Map<string,barItem>;
    setItems = setItems.bind(this);
}

Link to this code on typescript Playground

此代码引发编译器错误

Argument of type 'fooItem | barItem' is not assignable to parameter of type 'barItem'.
  Property 'compact' is missing in type 'fooItem' but required in type 'barItem'.

目的是使对象只能与其关联的类一起使用,但是我知道编译器无法保证它们是配对的。

什么是正确的打字稿语法,如果编译器知道项为fooItem,该选项将为Foo,反之亦然?

siqi111 回答:Typescript,如何在将项目添加到Map的两个类之间共享绑定的实用程序函数

如果您使用显式绑定函数而不是股票bind,则效果会更好,例如:

interface NamedItem {
  name: string
}

interface HasNamedMap<T extends NamedItem> {
  items: Map<string,T>;
}

function setItems<T extends NamedItem>(obj: HasNamedMap<T>): (item: T) => void {
  return function(item: T) {
    obj.items.set(item.name,item);
  }
}

这样可以正确地推断出特定的“项目”类型:

class Foo {
  items: Map<string,fooItem> = new Map();
  setItems = setItems(this); // inferred as (item: fooItem) => void
}

Playground

,

要达到您所要求的约束,我将创建一个generic函数,如下所示:

function setItems<T extends { name: string }>(
    this: { items: Map<string,T> },item: T
) {
    this.items.set(item.name,item); // okay
}

但是您会发现,如果尝试将其bind()设置为this,编译器会忘记item{name: string}窄,您会得到一些帮助太弱的类型,无法单独使用。

然后,您需要为每个类手动将setItems扩展为具体类型:

// separate var with annotation
const fooSetItems: (this: Foo,item: fooItem) => void = setItems;
class Foo {
    constructor() {
        this.items = new Map();
    }
    items: Map<string,fooItem>;
    setItems = fooSetItems.bind(this);
}


class Bar {
    constructor() {
        this.items = new Map();
    }
    items: Map<string,barItem>;

    // same var with assertion
    setItems = (setItems as (this: Bar,item: barItem) => void).bind(this);
}

这有效,但是比我喜欢的复杂。


建议:由于您已经将函数值属性附加到FooBar的每个实例中,而不是使用原型,因此我放弃了this参数完全使用一个简单的函数:

const bindSetItems =
    <N extends { name: string }>(ctx: { items: Map<string,N> }) =>
        (item: N) => ctx.items.set(item.name,item);

class Foo {
    constructor() {
        this.items = new Map();
    }
    items: Map<string,fooItem>;
    setItems = bindSetItems(this);
}

class Bar {
    constructor() {
        this.items = new Map();
    }
    items: Map<string,barItem>;
    setItems = bindSetItems(this);
}

行为相同,但是它不是使用this来搞乱,它只是使用currying而不是原型继承来完成工作。


希望其中一项能为您服务。祝你好运!

Link to code

本文链接:https://www.f2er.com/3164178.html

大家都在问