首页 > 解决方案 > 如何将联合类型解释为组件类型之一

问题描述

对于我正在做的一个小教育项目,我在工厂类中使用 Phaser CE 框架,这样Phaser.Text由它生成的元素会根据我使用预定义的 JSON 文件自动设置样式样式。

但是,我也有一个INamed属性,我坚持使用有名字的东西。这也有扩展Array<>,让我可以轻松地按名称从数组中获取项目。

因此,理论上我可以获取这样的数据:

样式.json:

{
    "text": [
        {
            "name": "default",
            "fill": "#FFF",
            "stroke": "#333",
            "strokeWidth": 1
        },
        {
            "name": "snazzy",
            "fill": "#F33",
            "stroke": "#633",
            "strokeWidth": 2
        },
    ]
}

...并使用我们的文本工厂...

文本工厂.ts:

namespace Main.UI {
    export class TextFactory {
        public styles: (Phaser.PhaserTextStyle & INamed)[] = [];

         public constructor() {}

         public initialize() {
             const data = game.cache.getJSON('ui-styles');
             const textStyles = data['text'];

             for (let current of textStyles) {
                 this.styles.push(current);
             }
        }

        public create(
            x: number,
            y: number,
            text: string,
            style?: string
        ): Phaser.Text {
            let styleData: (Phaser.PhaserTextStyle & INamed);
            if (!style)
                styleData = this.styles[0];
            else
                styleData = this.styles.getByName(style);

            // !!! Problem is here !!!
            const newText = game.add.text(x, y, text, <Phaser.PhaserTextStyle>styleData);
            return newText;
        }
    }
}

...并在游戏状态下使用它:

游戏状态.ts:

namespace Main.States {
    export class GameState extends Phaser.State {
        private textFactory: UI.TextFactory = null;

        public preload(): void {
            game.load.json('ui-styles', 'assets/data/styles.json');
        }

        public create(): void {
            this.textFactory = new UI.TextFactory();
            this.textFactory.initialize();

            const testText = this.textFactory.create(2, 2, "This should be white with a dark gray outline...");
            const otherText = this.textFactory.create(2, 52, "SNAZZY!", "snazzy");
        }
    }
}

如果您使用 Phaser 中的相关样板运行该常规设置,文本将呈现,但将是黑色且无样式。我已经进行了一些健全性检查,发现联合类型(Phaser.PhaserTextStyle & INamed)似乎给我带来了麻烦 - 调用game.add.text只需要Phaser.PhaserTextStyle.

我尝试将textStyle变量TextFactory.create转换为 a Phaser.PhaserTextStyle,但这似乎并不有效 - 我的文本仍然呈现无样式。我做了一些进一步的“愚蠢的开发者”检查,比如确保我已经保存并构建了我的更改,并添加了调试消息,但这些并没有产生任何见解来帮助了解我清楚地做了什么新的愚蠢的事情。因此,我的问题...

问题:我可以通过什么方式获取(A & B)TypeScript 中类型的对象并将其用作AorB类型?

标签: typescripttypescastingphaser-framework

解决方案


简短的回答:你不能。

长答案:唯一使用类型安全的方法是A & B使用类型保护。然而,即使这种方法也有局限性。AB

考虑以下边缘情况,其中两个形状都具有相同名称 ( id) 的字段:

interface A {
    name: string;
    id: string;
}

interface B {
    id: number;
}

type Intersection = A & B;

const test: Intersection = {
    id: 0, // Error — cannot be implemented! Nothing is both a string and a number
    name: ''
}

相交AB告诉 TypeScriptid属性需要同时是两者string & number,这是无法实现的。即使这样的概念存在于类型级别,它也不能有运行时表示。

即使——假设——我们可以有一个Intersection(有人可以使用类型断言),TypeScript 将允许我们编写会在运行时爆炸的代码。

declare function alpha(argument: A): void;
declare function beta(argument: B): void;

declare const intersection: Intersection;

alpha(intersection); // TypeScript allows that, but it can fail in runtime if `alpha` expects `id` to be of type `string`.

可以通过创建用户定义的类型保护来降低风险。

/**
 * Type-safe approach.
 */
declare function isA(argument: unknown): argument is A;
declare function isB(argument: unknown): argument is B;

if (isA(intersection)) {
    alpha(intersection);
}

if (isB(intersection)) {
    beta(intersection);
}

不幸的是,这些需要存在于运行时,并且没有什么可以阻止开发人员错误地实现它们。


推荐阅读