这是这question.人的后续行动

我想实现的目标是: 将多个子类传递给一个函数,有没有办法返回一个以"模式"形式传递的子类类型的对象?这样我就可以保留所有的智能感知功能.

低于我失败的try .

class Component {
    get name(): string {
        return this.constructor.name
    }
}

class Direction extends Component {
    x: number
    y: number

    constructor(x: number = 0, y: number = 0) {
        super()
        this.x = x
        this.y = y
    }
}

class Rectangle extends Component {
    x: number
    y: number
    w: number
    h: number

    constructor(x: number, y: number, w: number, h: number) {
        super()
        this.x = x
        this.y = y
        this.w = w
        this.h = h
    }
}

class Entity {
    components: Map<string, Component>

    constructor(...components: Component[]) {
        this.components = new Map()

        components.forEach(component => this.add(component))
    }

    get<C extends Component[]> (...components: { [P in keyof C]: new (...args: any[]) => C[P] }): Record<string, Component> {
      return components.reduce(
            (accumulator, component) => {
                accumulator[component.name.toLocaleLowerCase()] =
                    this.components.get(component.name)!

                return accumulator
            },
            {} as Record<string, Component>
        )
    }

    add(component: Component) {
        this.components.set(component.name, component)
    }
}

const player = new Entity(
                    new Rectangle(100, 100, 100, 100),
                    new Direction(1, 1)
                )

const p_components = player.get(Rectangle, Direction)

console.log(p_components.rectangle)
// Intellisense doens't know p_components properties
// Intellisense infers p_components.rectangle as Components - It should be Rectangle instead

我也在网上搜索过,我找到的最接近这个数字的是this.但我不知道如何在我的代码中实现它,以及它是否有帮助.

推荐答案

TypeScrip不会给出字符串literal typesthe name property of class constructors.相反,它只有string,这是真的,但对你试图做的事情没有帮助.有很多这样的请求,比如microsoft/TypeScript#47203,但它们已经被关闭,而支持microsoft/TypeScript#1579等待这样的时间,因为JavaScript实际上提供了一致的nameof运算符,这可能永远不会发生.

除非情况发生变化,否则,如果您希望使用强类型字符串来标识类,则必须手动维护它们.例如,您可以 for each 类实例提供需要实现的name个属性:

abstract class Component {
    abstract readonly name: string;
}

class Direction extends Component {
    readonly name = "Direction"
    // ✂ ⋯ rest of the class impl ⋯ ✂
}

class Rectangle extends Component {
    readonly name = "Rectangle"
    // ✂ ⋯ rest of the class impl ⋯ ✂
}

这是您需要手动执行的步骤.但是现在,如果您看一下Rectangle类型的name属性,您会发现它是强类型的:

type RectangleName = Rectangle['name']
// type RectangleName = "Rectangle"

好了,现在您可以编写您的get()方法了,与您之前的方法非常相似:

get<C extends Component[]>(
    ...components: { [P in keyof C]: new (...args: any) => C[P] }
): { [T in C[number] as Lowercase<T['name']>]: T } {
    return components.reduce(
        (accumulator, component) => {
            accumulator[component.name.toLowerCase()] =
                this.components.get(component.name)!

            return accumulator
        },
        {} as any
    )
}

输入类型相同,generic C的输入类型为mapped array/tuple type,Component个实例类型的数组的输入类型为constrained.因此,如果调用get(Rectangle, Direction),那么C将是元组类型[Rectangle, Direction].

输出类型是您关心的类型...在这种情况下,是{ [T in C[number] as Lowercase<T['name']>]: T }.这是类型C[number]key-remapped type,而类型C[number]是元素类型Cunion.(如果C是数组类型,并且您使用number类型的键对其进行索引,则您将获得该数组的一个元素.因此,indexed access type C[number]对应于数组的元素类型.有关详细信息,请参见Typescript interface from array type.)

对于C[number]中的每个组件类型T,我们希望输出中的键为Lowercase<T['name']>.也就是说,我们使用name索引到T以获得强类型名称,然后使用LowerCase<T> utility type将其转换为小写...与component.name.toLowerCase()的行为相对应.请注意,我将其从toLocaleLowerCase()更改为独立于区域设置的toLowerCase().TypeScrip完全不知道运行时将使用哪种语言环境,除非you知道,否则您不会想犯这样的错误,即假设语言环境的小写规则会将"Rectangle"转换为"rectangle".如果您有一个名为"Index"的组件,并且运行时区域设置恰好是土耳其语,那么它将变成"ındex"而不是"index",您将会遇到麻烦.

同样,对于C[number]中的每个组件类型T,键将是name属性的与区域设置无关的小写版本.而它的价值只会是T.因此,{ [T in C[number] as Lowercase<T['name']>]: T }.


让我们来测试一下:

const p_components = player.get(Rectangle, Direction);
// const p_components: {
//   direction: Direction;
//   rectangle: Rectangle;
// }

这看起来就是你想要的.

请注意,这只对您的类型有帮助,对实现没有真正的帮助.您可以使用从未被add()绑定的子类Component来调用player.get(),并在运行时当类型表明它将出现时获取undefined.您可以将实现修改为更通用,或者将类型设置为undefined,但我认为这超出了问题的范围.

Playground link to code

Typescript相关问答推荐

Angular -使用Phone Directive将值粘贴到控件以格式化值时验证器不工作

如何在排版中正确键入中间件链和控制器链

有没有办法解决相互影响的路由之间的合并?

获取函数中具有动态泛型的函数的参数

如果字符串文字类型是泛型类型,则用反引号将该类型括起来会中断

是否有一个版本的联合类型递归地使属性只出现在一个可选选项中?

我可以以这样一种方式键入对象,只允许它的键作为它的值吗?

Angular文件上传到Spring Boot失败,多部分边界拒绝

Angular 自定义验证控件未获得表单值

正确使用相交类型的打字集

有没有办法在Zod中使用跨字段验证来判断其他字段,然后呈现条件

字符串文字联合的分布式条件类型

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

typescript如何从映射类型推断

换行TypeScript';s具有泛型类型的推断数据类型

定义一个只允许来自另一个类型的特定字符串文字的类型的最佳方式

两个子组件共享控制权

具有枚举泛型类型的 Typescript 对象数组

可选通用映射器函数出错

如何使用 TypeScript 接口中第一个属性的值定义第二个属性