TypeScript 回调没有在其签名中完全实例化类

Posted

技术标签:

【中文标题】TypeScript 回调没有在其签名中完全实例化类【英文标题】:TypeScript callback does not fully instantiate class in its signature 【发布时间】: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 类中的其他成员。我只是想知道是否有解决这个问题的好方法?出于显而易见的原因,我宁愿避免实例化我自己的类并手动将传入数据中的值复制到其中。

【问题讨论】:

deserialize JSON to JAVASCRIPT object的可能重复 【参考方案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();

【讨论】:

以上是关于TypeScript 回调没有在其签名中完全实例化类的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript 类方法具有与构造函数相同的重载签名

调用实例方法“tableView”没有完全匹配

如何通过指定每个属性及其值来实例化 TypeScript 中的对象?

在Typescript中,如何在工厂(ish)函数中实例化的类上获取方法的通用返回类型

如何从另一个活动访问在一个活动中实例化并在其自己的线程中运行的对象?

在 TypeScript 中获取实例的类