打字稿动态 getter/setter

Typescript dynamic getter/setter

我一直在尝试让 Typescript 推断动态创建的 getter 和 setter 的类型。我有一个 class 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 类型

这是正确实施的方法吗?

你必须把class的public接口和实现细节分开考虑。

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

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

类型

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

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

或使用一组已知的键:

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

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


您需要通用 class 以便 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