【问题标题】:How can I inherit from a knockout class using Typescript?如何使用 Typescript 从淘汰赛类继承?
【发布时间】:2015-01-29 14:30:55
【问题描述】:

我正在使用带有 Typescript 的淘汰赛 3.x,我想创建一个继承自 KnockoutObservableArray<T> 的强类型集合类,以便我的类型可以是一个可观察的数组,但也有自己的方法。所以我尝试了这个:

export class Emails implements KnockoutObservableArray<Email>
{
    //add a new email to the list
    addItem() : void
    {
        var email = new ContactEmail();
        ...
        this.push(email);
    };
}

但这给了我以下错误:

Class Emails declares interface KnockoutObservableArray<Email> but does not implement it:
Type 'Emails' is missing property 'extend' from type 'KnockoutObservableArray<Email>'.

如果我尝试添加extend方法,则需要我实现peek方法,以此类推,提示我需要实现所有KnockoutObservableArray。但我不想重新实现它,我想扩展它,而且由于KnockoutObservableArray 是一个接口而不是一个类,我看不到任何方法可以做到这一点。有什么办法吗?

【问题讨论】:

  • 向可观察数组添加方法的典型方法是添加到ko.observableArray.fn 对象。我不确定 typescript 是否以及如何利用它。
  • 是添加到所有可观察数组还是仅添加一个实例?我想创建各种类型化的集合类,每个类都有自己的自己的方法,所以我只希望那些实例有方法,而不是所有数组。

标签: knockout.js typescript knockout-2.0


【解决方案1】:

在不久的过去,这也是困扰我的事情。

为什么?

很简单,有两个原因。

第一个原因是我没有真正从对象的角度理解 JavaScript。对我来说,它一直是一种“脚本语言”,就是这样,我听到人们告诉我不是这样,但我(仍然)是一个 C# 人,所以我基本上忽略了所说的话,因为它看起来不像C# 对象。

一切都变了,当我开始使用 Type Script 时,TS 以 JS 输出的形式向我展示了对象 JS 的样子。

第二个原因是 JS 的灵活性,更重要的是淘汰赛。

我喜欢在 C# 代码中简单地更改我的 JSON 端点的想法,除了对我的 HTML 进行一些调整之外,实际上不必在前端进行任何更改。

如果我想向输出对象添加一个新字段,那么我可以,然后只需向表中添加一个新列,绑定正确的字段名称并完成工作。

太棒了。

确实如此,直到我开始被要求提供基于行的功能。它从简单的请求开始,例如能够删除行和执行内联编辑等操作。

这导致了许多奇怪的冒险,例如:

Knockout JS + Bootstrap + Icons + html binding

还有这个:

Synchronizing a button class with an option control using knockoutjs

但是请求变得更加陌生和复杂,我开始想出各种疯狂的遍历 DOM 的方法,从我所在的行开始,向上到父级并返回到我的兄弟级,然后拉出文本来自相邻元素和其他疯狂。

然后我看到了光

我开始与一个只知道和/或生活在 JS 领域的初级开发人员合作一个项目,他只是问了我一个问题。

“如果这一切都让你如此痛苦,那你为什么不为你的行创建一个视图模型呢?”

于是下面的代码就诞生了。

var DirectoryEntryViewModel = (function ()
{
  function DirectoryEntryViewModel(inputRecord, parent)
  {
    this.Pkid = ko.observable(0);
    this.Name = ko.observable('');
    this.TelephoneNumber = ko.observable('');
    this.myParent = parent;

    ko.mapping.fromJS(inputRecord, {}, this);

  }

  DirectoryEntryViewModel.prototype.SomePublicFunction = function ()
  {
    // This is a function that will be available on every row in the array
    // You can call public functions in the base VM using "myParent.parentFunc(blah)"
    // you can pass this row to that function in the parent using  "myParent.someFunc(this)"
    // and the parent can simply do "this.array.pop(passedItem)" or simillar
  }

  return DirectoryEntryViewModel;
})();

var IndexViewModel = (function ()
{
  function IndexViewModel(targetElement)
  {
    this.loadComplete = ko.observable(false);
    this.phoneDirectory = ko.observableArray([]);
    this.showAlert = ko.computed(function ()
    {
      if (!this.loadComplete())
        return false;
      if (this.phoneDirectory().length < 1)
      {
        return true;
      }
      return false;
    }, this);

    this.showTable = ko.computed(function ()
    {
      if (!this.loadComplete())
        return false;
      if (this.phoneDirectory().length > 0)
      {
        return true;
      }
      return false;
    }, this);

    ko.applyBindings(this, targetElement);
    $.support.cors = true;
  }

  IndexViewModel.prototype.Load = function ()
  {
    var _this = this;
    $.getJSON("/module3/directory", function (data)
    {
      if (data.length > 0)
      {
        _this.phoneDirectory(ko.utils.arrayMap(data, function (item)
        {
          return new DirectoryEntryViewModel(item, _this);
        }));
      } else
      {
        _this.phoneDirectory([]);
      }
      _this.loadComplete(true);
    });
  };

  return IndexViewModel;
})();

window.onload = function ()
{
  var pageView = document.getElementById('directoryList');
  myIndexViewModel = new IndexViewModel(pageView);
  myIndexViewModel.Load();
};

现在这不是最好的例子(我刚刚从我必须手头的项目中抽出它),但它确实有效。

是的,您必须确保如果您在 JSON 中将字段添加到后端,您还将其添加到行视图模型(您可以使用 RequireJS 或类似的加载),以及添加该列在需要的地方添加到您的表格/列表/其他标记。

但是为了多出一两行,您可以轻松地添加一个函数,然后在集合中的每一行都可以使用该函数。

而且,在 typescript 的注释上,上面的整个 JS 是由 TS 编译器从实现相同模式的 TS 源生成的。

我有很多东西以这种方式运行(在我的 github 页面上有几个例子 - http://github.com/shawty 你可以克隆),我运行的一些应用程序有完整的视图模型适应整个 UI 基于计算的 observable 中的单个简单更改,我有管理自己状态的行(包括直接与数据库对话),然后在操作成功后更新它们在父表中的状态。

希望这会给您带来更多思考。虽然您可能会在尝试扩展现有的 KO 课程的道路上挣扎,但老实说,您会发现上述模式更容易掌握。

我曾经尝试过和你一样的方法,但是当我的 JS 朋友指出为行创建一个 VM 并重新使用它是多么容易时,我很快就放弃了。

希望对你有帮助 肖蒂

【讨论】:

  • 确实对每一行都有一个视图模型。我没有的是 collection 行的视图模型。该集合必须是一个淘汰的可观察数组,但我希望它具有自定义方法以及。在 .NET 中做到这一点的方法是继承,我正在尝试找到一种在 Typescript 中表示它的方法,因为它似乎无法直接继承。
  • 我也有用于集合的 VM,正如您在代码中看到的那样。我也有在某处生成它的 TS 代码。
【解决方案2】:

您实际上要查找的是 extends KnockoutObservableArray 的类,而不是 implements 它的类。

然而,扩展需要一个实现而不是一个接口,并且这个实现在 Typescript 中不可用。只有一个静态接口KnockoutObservableArrayStatic 返回一个实现,与knockout 的ko.observableArray 实现完全相似。

我用favoring composition over inheritance 解决了我的类似情况。例如:

export class Emails
{
    collection = ko.observableArray<Email>();

    //add a new email to the list
    addItem() : void
    {
        var email = new ContactEmail();
        ...
        this.collection.push(email);
    };
}

您可以访问集合属性以绑定到 DOM,或者如果您需要 observableArray 的任何功能:

<div data-bind="foreach: myEmails.collection">
...
</div>

【讨论】:

    【解决方案3】:

    implements 关键字用于声明您将实现提供的接口...要满足此条件,您需要实现以下接口中定义的所有方法(因为您需要遵循整个来自KnockoutObservableArray&lt;T&gt;的链。

    或者,创建一个较小的接口来表示您对自定义类的实际需求,如果它是所有这些的子集,您将能够将您的接口用于自定义类以及普通类KnockoutObservableArray&lt;T&gt; 类(将在结构上通过接口测试)。

    interface KnockoutObservableArray<T> extends KnockoutObservable<T[]>, KnockoutObservableArrayFunctions<T> {
        extend(requestedExtenders: { [key: string]: any; }): KnockoutObservableArray<T>;
    }
    
    interface KnockoutObservable<T> extends KnockoutSubscribable<T>, KnockoutObservableFunctions<T> {
        (): T;
        (value: T): void;
    
        peek(): T;
        valueHasMutated?:{(): void;};
        valueWillMutate?:{(): void;};
        extend(requestedExtenders: { [key: string]: any; }): KnockoutObservable<T>;
    }
    
    interface KnockoutObservableArrayFunctions<T> {
        // General Array functions
        indexOf(searchElement: T, fromIndex?: number): number;
        slice(start: number, end?: number): T[];
        splice(start: number): T[];
        splice(start: number, deleteCount: number, ...items: T[]): T[];
        pop(): T;
        push(...items: T[]): void;
        shift(): T;
        unshift(...items: T[]): number;
        reverse(): T[];
        sort(): void;
        sort(compareFunction: (left: T, right: T) => number): void;
    
        // Ko specific
        replace(oldItem: T, newItem: T): void;
    
        remove(item: T): T[];
        remove(removeFunction: (item: T) => boolean): T[];
        removeAll(items: T[]): T[];
        removeAll(): T[];
    
        destroy(item: T): void;
        destroy(destroyFunction: (item: T) => boolean): void;
        destroyAll(items: T[]): void;
        destroyAll(): void;
    }
    
    interface KnockoutSubscribable<T> extends KnockoutSubscribableFunctions<T> {
        subscribe(callback: (newValue: T) => void, target?: any, event?: string): KnockoutSubscription;
        subscribe<TEvent>(callback: (newValue: TEvent) => void, target: any, event: string): KnockoutSubscription;
        extend(requestedExtenders: { [key: string]: any; }): KnockoutSubscribable<T>;
        getSubscriptionsCount(): number;
    }
    
    interface KnockoutSubscribableFunctions<T> {
        notifySubscribers(valueToWrite?: T, event?: string): void;
    }
    
    interface KnockoutObservableFunctions<T> {
        equalityComparer(a: any, b: any): boolean;
    }
    

    这是替代方案的一个示例...假设您需要 pushremove 作为示例。 (旁注:这里不可能继承,因为这些都是接口 - 如果 Knockout 支持继承,则可以更改类型定义以允许无工作 extends 而不是辛勤工作 implements 这个答案的版本.. .)

    这个例子中有趣的一点是第一个接口 - 它是您定义的自定义接口,但任何匹配它的东西都可以,包括KnockoutObservableArray&lt;Email&gt;。这是一个结构类型的例子,可以节省一些时间。结构化类型要你去酒吧。

    /// <reference path="scripts/typings/knockout/knockout.d.ts" />
    
    interface MyObservableArray<T> {
        push(...items: T[]): void;
        remove(item: T): T[];
    }
    
    class Email {
        user: string;
        domain: string;
    }
    
    class ObservableEmailArray implements MyObservableArray<Email> {
        push(...items: Email[]): void {
    
        }
    
        remove(item: Email): Email[] {
            return [];
        }
    }
    
    var observableArrayOne: KnockoutObservableArray<Email> = ko.observableArray<Email>();
    var observableArrayTwo: MyObservableArray<Email> = new ObservableEmailArray();
    
    function example(input: MyObservableArray<Email>) {
        // Just an example - will accept input of type MyObservableArray<Email>...
    }
    
    example(observableArrayOne);
    
    example(observableArrayTwo);
    

    【讨论】:

    • 实现整个链将是完成一个非常简单的继承情况的大量工作。你的第二段听起来更有希望,但我不太明白你的意思。
    • 添加的示例是否有助于解释?
    • 如果您要做的只是为传递给表格的行添加功能,我可以向您展示一种更简单的方法,而不是重新定义 observablearray。
    • @SteveFenton:你是说因为 KO 对象是接口,所以它们根本无法扩展?我可以理解,虽然它有点限制,这是我在 .NET 中一直在做的事情,并且底层的 javascript 支持它,所以我想有一种方法可以在 Typescript 中表达它。在您的示例中,您似乎必须重新实现整个界面 - 或您必须决定的某个子集 - 我认为这太杂乱无用了。
    • @shawty:我正在尝试向行集合添加功能,但我愿意接受其他方式来实现这一点。你会推荐什么?
    【解决方案4】:

    我遇到了同样的问题并找到了解决方法。

       export class ShoppingCartVM {
            private _items: KnockoutObservableArray<ShoppingCartVM.ItemVM>;
            constructor() {
                this._items = ko.observableArray([]);
                var self = this;
                (<any>self._items).Add = function() { self.Add(); }
                return <any>self._items;
            }
    
            public Add() {
                this._items.push(new ShoppingCartVM.ItemVM({ Product: "Shoes", UnitPrice: 1, Quantity: 2 }));
            }
        }
    

    您不能将 ShoppingCartVM 转换为 KnockoutObservableArray,但您可以像 Steve Fenton 回答的那样引入通用接口。

    【讨论】:

      猜你喜欢
      • 2017-07-07
      • 1970-01-01
      • 1970-01-01
      • 2019-03-31
      • 2012-10-22
      • 2014-06-15
      • 1970-01-01
      • 2017-02-15
      • 2014-01-03
      相关资源
      最近更新 更多