首页 文章

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

提问于
浏览
0

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

但是,我还有一个 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 ,但这并没有保存并构建我的更改,并添加了调试消息,但那些没有明确完成 . 因此,我的问题......

Question: 以什么方式可以在TypeScript中使用 (A & B) 类型的对象,并将其用作 AB 类型?

2 回答

  • 1

    我不认为对象的类型与问题有任何关系 .

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

    类型断言在运行时不执行任何操作,因此您可以使用它们来删除属性,您可以仅使用它们来更改编译器知道的类型(在这种情况下无效) .

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

    delete styleData['name']
    

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

    let { name, ...styleDataNoName } = styleData;
    
  • 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);
    }
    

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

相关问题