【问题标题】:TypeScript callback does not fully instantiate class in its signatureTypeScript 回调没有在其签名中完全实例化类
【发布时间】:2015-03-13 08:27:04
【问题描述】:

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

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 无法知道 TypeScript 类中的其他成员。我只是想知道是否有解决这个问题的好方法?出于显而易见的原因,我宁愿避免实例化我自己的类并手动将传入数据中的值复制到其中。

【问题讨论】:

标签: jquery ajax json typescript


【解决方案1】:

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

您必须为 AJAX 请求创建一个拦截器,该拦截器获取返回的原始对象并为您构造类的实例。这可以根据每个类/请求单独完成,或者如果您有数据返回要实例化的类并且每个类的构造函数都接受这样的对象,则可以全局执行。然后您可以编写一个通用拦截器将原始对象转换为正确类的实例。

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

【讨论】:

    【解决方案2】:

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

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

    静态方法

    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();
    

    您甚至可以通过在任意两个对象的“左”和“右”参数中查找匹配的属性来自动将属性映射到新实例,尽管这比普通映射更抽象。

    【讨论】:

      【解决方案3】:

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

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

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

      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();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-15
        • 2022-09-28
        • 1970-01-01
        • 2014-01-29
        • 1970-01-01
        • 2019-04-06
        • 2022-11-09
        • 1970-01-01
        相关资源
        最近更新 更多