0

我一直试图让 Typescript 推断出动态创建的 getter 和 setter 的类型。我有一堂MyClasscontainers地图的课:

type Container = {
    get: () => Content
    set: (value: Content) => void
}

type ContainersMap = { [key: string]: Container }

class MyClass {
    containers: ContainersMap

    constructor(names: string[]) {
        this.containers = {} as ContainersMap

        names.forEach(name => {
            Object.defineProperty(this.containers, name, {
                get() { return read(name) },
                set(value) { write(name, value) }
            })
        })

        Object.freeze(this.containers)
    }
}

接下来在我的代码中,我想像这样使用它:

let someContent: Content = {/* some content */}

let myClass = new MyClass(['first', 'second'])

// Getting type error in line below because TS thinks I try assign Content to Container
myClass.containers['first'] = someContent 

我发现的一个可能的解决方案是定义type ContainersMap = { [key: string]: Content },但我不喜欢这个解决方案,因为我认为它不能反映实际ContainersMap类型

这是一种正确实施的方法吗?

4

1 回答 1

1

您必须将类的公共接口与实现细节分开考虑。

type Container = {
    get: () => Content
    set: (value: Content) => void
}

这种类型定义了一个具有属性get和的对象set。这意味着您可以调用myClass.containers.first.set(someContent). 但是你不能这样做myClass.containers.first = someContent,因为myClass.containers.first应该是 type Container,而不是 type Content

get方法set应该只是一个实现细节。它们是您的类实现您想要的接口的方式——它是myClass.containers.first类型的可读和可写属性Content。该接口的公共“合同”非常简单。

type ContainersMap = { [key: string]: Content }

或使用一组已知的键:

type ContainersMap<K extends string> = { [key in K]: Content } // same as Record<K, Content>

由于没有初始值,您可能希望将属性设为可选。


您将需要一个通用类,以便ContainersMap了解您的特定键。我不确定你的代码中的readandwrite方法是什么,所以我只是让它们保持原样。这基本上就是你想要的:

class MyClass<K extends string> {
    containers: ContainersMap<K>

    constructor(names: K[]) {
        this.containers = {} as ContainersMap<K>

        names.forEach(name => {
            Object.defineProperty(this.containers, name, {
                get() { return read(name) },
                set(value) { write(name, value) }
            })
        })

        Object.freeze(this.containers)
    }
}

您根本不再需要该类型Container


这允许您获取和设置已知属性:

let myClass = new MyClass(['first', 'second'])

myClass.containers.first = someContent;

console.log(myClass.containers.first);

但是您不能访问其他属性:

// Error: Property 'third' does not exist on type 'ContainersMap<"first" | "second">'
myClass.containers.third = someContent;

打字稿游乐场链接

于 2021-04-14T00:22:36.070 回答