微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

打字稿动态getter/setter

如何解决打字稿动态getter/setter

我一直在试图让 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 类型

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

解决方法

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。