Knockout.js 如何使用Typescript从淘汰类继承?

Knockout.js 如何使用Typescript从淘汰类继承?,knockout.js,typescript,knockout-2.0,Knockout.js,Typescript,Knockout 2.0,我将knockout 3.x与Typescript一起使用,我想创建一个强类型集合类,该类继承自knockout ObservableArray,这样我的类型既可以是一个可观察数组,也可以有自己的方法。所以我试了一下: export class Emails implements KnockoutObservableArray<Email> { //add a new email to the list addItem() : void { v

我将knockout 3.x与Typescript一起使用,我想创建一个强类型集合类,该类继承自
knockout ObservableArray
,这样我的类型既可以是一个可观察数组,也可以有自己的方法。所以我试了一下:

export class Emails implements KnockoutObservableArray<Email>
{
    //add a new email to the list
    addItem() : void
    {
        var email = new ContactEmail();
        ...
        this.push(email);
    };
}
导出类邮件实现knockoutobserray
{
//向列表中添加新电子邮件
addItem():void
{
var email=新联系人电子邮件();
...
这个.推送(邮件),;
};
}
但这给了我以下错误:

Class Emails declares interface KnockoutObservableArray<Email> but does not implement it:
Type 'Emails' is missing property 'extend' from type 'KnockoutObservableArray<Email>'.
Class Emails声明了接口KnockoutObservableArray,但没有实现它:
类型“Emails”缺少类型“knockoutobserarray”的属性“extend”。

如果我尝试添加
extend
方法,那么它要求我实现
peek
方法,依此类推,这表明我需要实现所有
knockoutobserray
。但是我不想重新实现它,我想扩展它,因为
knockoutobservearray
是一个接口而不是一个类,所以我看不到任何方法来实现它。有办法吗?

使用
implements
关键字声明您将实现提供的接口。。。要满足此条件,您需要实现以下接口中定义的所有方法(因为您需要遵循
knockoutobserray
中的整个链)

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


在不远的过去,这件事也曾困扰过我

为什么?

很简单,有两个原因。

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

所有这些都改变了,当我开始使用类型脚本时,TS以JS输出的形式向我展示了对象JS的样子

第二个原因是JS的灵活性,更重要的是击倒

我喜欢这样的想法:只需在C#代码中更改JSON端点,而不必在前端进行任何更改,只需对HTML进行一些调整

如果我想向输出对象添加一个新字段,那么我可以,然后简单地向表添加一个新列,绑定正确的字段名和完成的工作

非常好

事实上是这样,直到我开始被要求提供基于行的功能,它从简单的请求开始,比如能够删除行和进行内联编辑

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

这是:

但是请求变得越来越奇怪和复杂,我开始用越来越疯狂的方式来遍历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();
};
var DirectoryEntryViewModel=(函数()
{
函数DirectoryEntryViewModel(inputRecord,父级)
{
此.Pkid=ko.可观测(0);
this.Name=ko.observable(“”);
此电话号码=可观察到的ko(“”);
this.myParent=parent;
fromJS(inputRecord,{},this);
}
DirectoryEntryViewModel.prototype.SomePublicFunction=函数()
{
//这是一个可用于数组中每一行的函数
//您可以使用“myParent.parentFunc(blah)”在基本VM中调用公共函数
//您可以使用“myParent.someFunc(this)”将此行传递给父函数中的该函数
//父级可以简单地执行“this.array.pop(passedItem)”或类似操作
}
返回DirectoryEntryViewModel;
})();
var IndexViewModel=(函数()
{
函数索引模型(targetElement)
{
this.loadComplete=ko.可观察(假);
this.phoneDirectory=ko.observearray([]);
this.showarter=ko.computed(函数()
{
如果(!this.loadComplete())
返回false;
if(this.phoneDirectory().length<1)
{
返回true;
}
返回false;
},这个);
this.showTable=ko.computed(函数()
{
如果(!this.loadComplete())
返回false;
if(this.phoneDirectory().length>0)
{
返回true;
}
返回false;
},这个);
ko.应用绑定(本,targetElement);
$.support.cors=true;
}
IndexViewModel.prototype.Load=函数()
{
var_this=这个;
$.getJSON(“/module3/directory”),函数(数据)
{
如果(data.length>0)
{
_this.phoneDirectory(ko.utils.arrayMap)(数据、函数(项)
{
返回新的DirectoryEntryViewModel(项,\u this);
}));
}否则
{
_这个.phoneDirectory([]);
}
_此.loadComplete(true);
});
};
返回索引模型;
})();
window.onload=函数()
{
var pageView=document.getElementById('directoryList');
myIndexViewModel=新的IndexViewModel(页面视图);
myIndexViewModel.Load();
};
现在,这并不是想象中最好的例子(我刚刚从一个我不得不着手的项目中把它拽了出来),但它是有效的

是的,您必须确保如果您在JSON中向后端添加了字段,那么您也会添加
/// <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);
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();
};
   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 }));
        }
    }
export class Emails
{
    collection = ko.observableArray<Email>();

    //add a new email to the list
    addItem() : void
    {
        var email = new ContactEmail();
        ...
        this.collection.push(email);
    };
}
<div data-bind="foreach: myEmails.collection">
...
</div>