Typescript: 如何为类注册表变量定义类型?

huangapple go评论72阅读模式
英文:

Typescript: How can one type a class registry variable?

问题

I've translated the code portion as requested:

我正在尝试根据用户定义的配置(以JSON或YAML形式传递)动态构建对象。键是表示类名的字符串,值是构造函数参数。

代码定义了一些从抽象类继承的类,并将这些类放入一个注册表中,其中键=类名,值=类类型。每个构造函数使用对象解构来获取其参数。
然后,一个函数会遍历配置,对于每个键(类名):

- 查找注册表中相应的类型
- 通过调用构造函数并传入解构后的对象来创建类的新实例
- 调用抽象基类中定义的公共函数

首先出现的问题是注册表的类型是什么?

第二个问题是如何对配置进行类型化,以便仍然可以调用构造函数?

下面是一个示例代码,它无法编译通过。它采用了注册表方法。

I've provided the translation of the code portion. If you have any specific questions or need further assistance with this code, please let me know.

英文:

I'm trying to build objects dynamically depending on user defined configuration (passed as a JSON or YAML). The keys are strings representing class names and the values constructor parameters.

The code defines a few classes that inherit from an abstract class and puts the classes in a registry where key = class name, value = class type. Each constructor uses object decomposition to get its parameters.
A function then takes the configuration and for each key (class name):

  • looks up the corresponding type in the registry
  • creates a new instance of the class by calling the constructor with an object that is decomposed
  • calls a common function defined in the abstract base class

The first question that arises in how is the registry typed?

The second is how can the config be typed so that the constructors can still be called?

Below is example code that fails to compile. It takes the registry approach.

abstract class BaseClass {
    abstract doSomething(): void
}

// Param names are inconsequential
// They are here to show that constructor signatures vary
interface ComplexSomethingParam {
    key: string
    key2: number
}
interface SomethingParams {
    param1: string
    param2: number
    param3: ComplexSomethingParam
}

class Something extends BaseClass {
    readonly param1: string
    readonly param2: number
    readonly param3: ComplexSomethingParam
    constructor({ param1, param2, param3 }: SomethingParams) {
        super()
        this.param1 = param1
        this.param2 = param2
        this.param3 = param3
    }
    doSomething(): void {
        console.log("I am something !")
    }
}


interface ComplexAnotherParam {
    anotherKey: string
    anotherKey2: number
}
interface AnotherParams {
    anotherParam1: string
    anotherParam2: number
    anotherParam3: ComplexAnotherParam
}

class AnotherThing extends BaseClass {
    readonly anotherParam1: string
    readonly anotherParam2: number
    readonly anotherParam3: ComplexAnotherParam
    constructor({ anotherParam1, anotherParam2, anotherParam3 }: AnotherParams) {
        super()
        this.anotherParam1 = anotherParam1
        this.anotherParam2 = anotherParam2
        this.anotherParam3 = anotherParam3
    }
    doSomething(): void {
        console.log("I am another thing !")
    }
}

// To be consulted when dynamically creating objects
// Should store the class types and their names
// QUESTION: Which types should be used here
const REGISTRY: Record<string, BaseClass> = {
    Something,
    AnotherThing
}

// <class name>: <constructor parameters>
const CONFIG = {
    "Something": {
        "param1": "value",
        "param2": 1234,
        "param3": {
            "key1": "value",
            "key2": 456
        }
    },

    "AnotherThing": {
        "anotherParam1": "value",
        "anotherParam2": 1234,
        "anotherParam3": {
            "anotherKey1": "value",
            "anotherKey2": 456
        }
    }
}

// QUESTION: which types should be used for the config param?
function handleConfig(config) {
    for (const className of config) {
        const klass = REGISTRY[className]
        if (klass === undefined) {
            console.error(`Unknown permission class ${className}`)
            return
        }
        // END GOAL: successfully create an object and call the doSomething method!
        const instance = new klass(config[className])
        instance.doSomething()
    }
}

handleConfig(CONFIG)

Here the typescript playground link.

答案1

得分: 0

以下是您提供的代码的翻译部分:

const REGISTRY = {
    Something,
    AnotherThing
    // 确保此类型正确,不限制其类型
    // 确保它们都是单参数构造函数 = BaseClass
} 满足 Record<string, new (onlyArg: any) => BaseClass>

// 将每个键映射到构造函数参数
type ConfigType = { [K in keyof typeof REGISTRY]?: ConstructorParameters<typeof REGISTRY[K]>[0] }

// <类名>: <构造函数参数>
const CONFIG = {
    "Something": {
        "param1": "value",
        "param2": 1234,
        "param3": {
            "key1": "value", // 错误
            "key2": 456
        }
    },

    "AnotherThing": {
        "anotherParam1": "value",
        "anotherParam2": 1234,
        "anotherParam3": {
            "anotherKey1": "value", // 错误
            "anotherKey2": 456
        }
    }
    // 确保此类型正确,不限制其类型
} 满足 ConfigType

// 类型魔法,以更好地类型化内置函数
const recordEntries = Object.entries as (<T>(o: T) => ({ [K in keyof T]-?: [K, T[K]] }[keyof T])[])

function handleConfig(
    config: ConfigType
) {
    for (const [className, constructorArg] of recordEntries(config)) {
        const klass = REGISTRY[className]
        if (klass === undefined) {
            console.error(`未知的权限类 ${className}`)
            return
        }
        // 无法在这里提供帮助,必须使用 anycast
        const instance = new klass(constructorArg as any)
        instance.doSomething()
    }
}
// 当然会报错,因为CONFIG不满足要求
handleConfig(CONFIG)

如果您需要进一步的帮助或有其他问题,请随时提出。

英文:

playground: https://tsplay.dev/NB8bxW

const REGISTRY = {
    Something,
    AnotherThing
    // ensure this type is correct, without limiting its type
    // ensure all of them are single-argument constructors = BaseClass
} satisfies Record&lt;string, new (onlyArg: any) =&gt; BaseClass&gt;

// Map each key to constructor argument
type ConfigType = { [K in keyof typeof REGISTRY]?: ConstructorParameters&lt;typeof REGISTRY[K]&gt;[0] }

// &lt;class name&gt;: &lt;constructor parameters&gt;
const CONFIG = {
    &quot;Something&quot;: {
        &quot;param1&quot;: &quot;value&quot;,
        &quot;param2&quot;: 1234,
        &quot;param3&quot;: {
            &quot;key1&quot;: &quot;value&quot;, // error
            &quot;key2&quot;: 456
        }
    },

    &quot;AnotherThing&quot;: {
        &quot;anotherParam1&quot;: &quot;value&quot;,
        &quot;anotherParam2&quot;: 1234,
        &quot;anotherParam3&quot;: {
            &quot;anotherKey1&quot;: &quot;value&quot;, // error
            &quot;anotherKey2&quot;: 456
        }
    }
    // ensure this type is correct, without limiting its type
} satisfies ConfigType

// type magic to better type the builtin function
const recordEntries = Object.entries as (&lt;T&gt;(o: T) =&gt; ({ [K in keyof T]-?: [K, T[K]] }[keyof T])[])

function handleConfig(
    config: ConfigType
) {
    for (const [className, constructorArg] of recordEntries(config)) {
        const klass = REGISTRY[className]
        if (klass === undefined) {
            console.error(`Unknown permission class ${className}`)
            return
        }
        // can&#39;t help here, have to anycast
        const instance = new klass(constructorArg as any)
        instance.doSomething()
    }
}
// ofc errors because CONFIG does not satisfy
handleConfig(CONFIG)

huangapple
  • 本文由 发表于 2023年4月19日 18:07:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76053246.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定