TypeScript 回调未在其签名中完全实例化 class

TypeScript callback does not fully instantiate class in its signature

我在 TypeScript 中有一个 class,它对服务进行 Ajax 调用,然后在 return 中获取一个 JSON 对象;非常标准的东西。我希望 Ajax 调用的成功处理程序接受传入的 JSON 作为在 TypeScript 中定义的实例化 class,即我要使用强类型 Ajax这里。这主要有效。我发现我可以很好地接收传入数据,但是 JSON 响应中不存在的任何 class 成员(属性和方法)在运行时都不会出现在对象上。这是一些演示问题的代码。

module Samples {

    export class MyData {
        public A: number;
        public B: number;
        public get C(): number { return this.A + this.B; }
    }

    export class ClassDemo {
        constructor(){

            var ajaxSettings: JQueryAjaxSettings = {
                url: "/api/build/sample",  // Returns {"A":6,"B":4}
                type: "GET",
                dataType: "json",
                cache: false
            };

            var jqXhr: JQueryXHR = $.ajax(ajaxSettings);
            jqXhr.done(this.loadSucceeded);
            jqXhr.fail( /* ... */);
        }

        private loadSucceeded(data: MyData, text: string, jqXhr: JQueryXHR) {

            // 
            // Displays "6 + 4 = undefined"
            //
            alert(data.A + " + " + data.B + " = " + data.C);
        }
    }
}

var sample = new Samples.ClassDemo();

我想这是有道理的,因为它是 JQuery 在运行时在 JavaScript 中创建对象,而不是 TypeScript,因此 JQuery 无法知道打字稿 class。我只是想知道是否有解决此问题的好方法?出于显而易见的原因,我宁愿避免实例化我自己的 class 并手动将传入数据中的值复制到其中。

我认为您无法做任何事情来让 jQuery 自动提供值作为您的 MyData class 的实例,而不仅仅是一个原始对象。因为最终,jQuery 所做的只是使用 JSON.parse() 反序列化 JSON 文本,并将生成的 JS 对象传递给 done() 回调。 JSON 不支持说对象是 class.

的实例

您必须为 AJAX 请求创建一个拦截器,该拦截器采用原始对象 returned 并为您构建 class 的实例。这可以根据 class/request 单独完成,或者如果您有数据 return 和 class 进行实例化并且每个 class' 构造函数都接受这样的对象,则可以全局完成。然后你可以编写一个通用拦截器将原始对象转换为正确 class.

的实例

当然,您的服务器代码要么必须知道客户端类型,要么您必须将类型指定为请求的一部分,并 return 在响应中指定类型。

如果我简化示例(删除 AJAX 调用,但留下将简单数据对象转换为 MyData 实例的重要问题),您可以通过以下方式解决问题将纯数据映射到新的 MyData 实例。

例如,我使用了静态方法。您可以在这里选择自己的模式。也许您会接受 AB 作为构造函数参数,或者使用映射器 class 或应用您认为最合适的任何模式:

静态方法

module Samples {

    export class MyData {
        public A: number;
        public B: number;
        public get C(): number { return this.A + this.B; }

        public static fromJson(json: { A: number; B: number}) {
            var myData = new MyData();
            myData.A = json.A;
            myData.B = json.B;
            return myData;
        }
    }

    export class ClassDemo {
        constructor(){
            var result = { A: 1, B: 2};
            this.loadSucceeded(MyData.fromJson(result));
        }

        private loadSucceeded(data: MyData) {
            alert(data.A + " + " + data.B + " = " + data.C);
        }
    }
}

var sample = new Samples.ClassDemo();

构造器

module Samples {

    export class MyData {
        constructor(public A: number, public B:number){}

        public get C(): number { return this.A + this.B; }
    }

    export class ClassDemo {
        constructor(){
            var result = { A: 1, B: 2};
            this.loadSucceeded(new MyData(result.A, result.B));
        }

        private loadSucceeded(data: MyData) {
            alert(data.A + " + " + data.B + " = " + data.C);
        }
    }
}

var sample = new Samples.ClassDemo();

您甚至可以通过在任何两个对象的 "left" 和 "right" 手动参数中查找匹配的属性来自动将属性映射到新实例,尽管这比普通映射。

感谢大家的讲解和建议。我想我最终选择了所有这些的混合体,这就是我提供自己的答案的原因。

我在实际代码中的几个地方已经得到了我描述的模式,所有涉及的 classes 都不止两个成员。在这个开发阶段,它们也可能会发生变化,因此创建一个通用助手 class 来将成员值从一个对象映射到另一个对象是有意义的。在我的场景中,将此映射器作为构造函数的一部分调用也是有意义的,因为某些 classes 具有一些额外的设置操作。构造函数现在采用 class 的现有实例的可选参数来从中复制数据。

结果如下。最后一个演示案例是我的代码中最常使用的模式。

module Samples {

    export class Helpers {
        public static Recreate<T>(typeT: { new (): T; }, sourceObj: T, targetObj?: T) {

            if (targetObj == null) {
                targetObj = new typeT();
            }

            for (var property in sourceObj) {
                targetObj[property] = sourceObj[property];
            }

            return targetObj;
        }
    }

    export class MyData {

        constructor(fromObj?: MyData) {
            this.C = 0;

            if (fromObj != null) {
                Helpers.Recreate(MyData, fromObj, this);
            }

            this.C += 2;
        }

        public A: number;
        public B: number;
        public C: number;
        public get D(): number { return this.A + this.B + this.C; }
    }

    export class ClassDemo {
        constructor() {

            var ajaxSettings: JQueryAjaxSettings = {
                url: "/api/build/sample", // Returns {"A":6,"B":4}
                type: "GET",
                dataType: "json",
                cache: false
            };

            var jqXhr: JQueryXHR = $.ajax(ajaxSettings);
            jqXhr.done(this.loadSucceeded);
            jqXhr.fail( /* ... */);
        }

        private loadSucceeded(data: MyData, text: string, jqXhr: JQueryXHR) {

            // Illustrates the problem that MyData.C and MyData.D are undefined even though the type is in the signature
            // Displays "6 + 4 + undefined = undefined"
            alert(data.A + " + " + data.B + " + " + data.C + " = " + data.D);

            // The default output for a brand new MyData object; undefined and NaN values are as expected
            // Displays "undefined + undefined + 2 = NaN"
            var plainData: MyData = new MyData();
            alert(plainData.A + " + " + plainData.B + " + " + plainData.C + " = " + plainData.D);

            // This creates a real instance of MyData using the helper method directly and produces the right output
            // Displays "6 + 4 + 2 = 12"
            var recreatedData: MyData = Helpers.Recreate(MyData, data);
            alert(recreatedData.A + " + " + recreatedData.B + " + " + recreatedData.C + " = " + recreatedData.D);

            // This uses the helper method located in the constructor to rebuild 'data' from itself and produces the right output
            // Displays "6 + 4 + 2 = 12"
            data = new MyData(data);
            alert(data.A + " + " + data.B + " + " + data.C + " = " + data.D);
        }
    }
}

var sample = new Samples.ClassDemo();