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

How to interpret a union type as one of the component types

对于我正在做的一个小的教育性副项目,我在工厂中使用 Phaser CE 框架 class,因此它生成的 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.

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

问题: 我可以用什么方式在 TypeScript 中获取类型为 (A & B) 的对象并直接使用它作为 AB 类型?

简答:不能。

长答案:将 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: ''
}

Intersecting A and B 告诉 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);
}

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

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

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

类型断言在运行时不做任何事情,所以你可以用它们来删除一个 属性,你可以用它们只是改变编译器知道的类型(在这种情况下没有帮助) .

您可以使用 delete 删除多余的 属性 :

delete styleData['name'] 

或者您可以创建一个没有 属性 的新对象并使用它,使用传播:

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