如何将联合类型解释为组件类型之一

时间:2018-12-08 22:12:15

标签: typescript types casting phaser-framework intersected-types

对于我正在从事的一些教育性副项目,我在工厂类中使用了Phaser CE框架,这样,根据生成的Phaser.Text元素,可以根据我使用预定义样式编写的JSON文件。

但是,我还有一个INamed属性,可以粘贴到带有名称的内容上。这在Array<>上也有扩展名,使我可以轻松地从名称中获取数组中的项。

因此,从理论上讲,我可以这样采集数据:

styles.json:

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

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

TextFactory.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;
        }
    }
}

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

GameState.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变量强制转换为Phaser.PhaserTextStyle,但这似乎没有效果-我的文本仍然呈现未样式。我进行了进一步的“ Silly Developer”检查,例如确保已保存并构建了更改,并添加了调试消息,但是这些并没有产生任何见解,可以帮助您了解我所做的新的愚蠢的事情。因此,我的问题...

问题: :我可以采用哪种方式在TypeScript中使用(A & B)之类的对象并将其用作AB类型?

2 个答案:

答案 0 :(得分:1)

简短的回答:您不能。

长答案:将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告诉TypeScript id属性必须同时为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);
}

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

答案 1 :(得分:1)

我认为对象的类型与问题无关。

game.add.text函数可能在对象上具有额外的name属性。

类型断言在运行时不做任何事情,因此您可以使用它们来删除属性,可以使用它们来更改编译器知道的类型(在这种情况下无济于事)。

您可以使用delete删除额外的属性:

delete styleData['name'] 

或者您可以创建一个不带属性的新对象,并使用spread来使用它:

let { name, ...styleDataNoName } = styleData;