打字稿动态getter/setter

时间:2021-04-13 23:34:14

标签: typescript accessor

我一直在试图让 Typescript 推断动态创建的 getter 和 setter 的类型。我有一个带有 MyClass 地图的 containers 类:

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 类型

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

1 个答案:

答案 0 :(得分:1)

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

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

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

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

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

或使用一组已知的键:

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

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


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

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;

Typescript Playground Link