【问题标题】:(TypeScript) Extending KnockoutObservableArray(TypeScript) 扩展 KnockoutObservableArray
【发布时间】:2018-07-09 20:41:32
【问题描述】:

我想创建一个松散为MyKOArray<T> extends KnockoutObservableArray<T> 的 TypeScript 类。这甚至可能吗?如果是这样,是否有一种紧凑的方法可以做到这一点?

  • 我阅读了 Knockout 的 extenders 部分,但它似乎不适用,因为我正在尝试(除其他外)添加 state 属性 - 不影响现有的可订阅。
  • 我可以列出我尝试过的事情以及它们出错的方式(例如 TS2507、TS2322 等),但我怀疑它们是否会提供信息。作为基线,我相信一些基本细节是:
    • KnockoutObservableArray 是一个接口,所以我真的需要“扩展”ko.observableArray(“工厂”功能)。
    • ko.observableArray 的签名不正确(即没有 new,它处理函数中的初始值而不是 constructor 方法)。
    • 我可以通过构造函数return ko.observableArray(initial),但无法正确获取原型链(例如,方法不可用)。
    • 我可以将 JS 中的 override @@create 转换为 return ko.observableArray(initial),但是我很难找到 JS 示例(更不用说有效的 TS 实现了)。
  • 我还发现了一个类似主题的extremely old discussion。那里的结论是,由于 KO 缺乏原型设计,这是不可能的,但当前的源代码(例如ko.utils.setPrototypeOfOrExtend)表明这个结论可能已经过时了。
  • 我还研究了很多通用的 TS 扩展问题(例如 Can you extend a function in TypeScript?),但没有找到似乎匹配的内容。

===

编辑:为什么?

我正在尝试使用以下模式解耦我的系统(仅记录“有趣的案例”):

  • ViewModel 向数据服务发出请求(间接通过类的工厂方法)。数据服务立即返回一个实例(MyClassKnockoutObservableArray<MyClass> 分别用于 Singleton 或 Collection)。
  • 服务以异步方式加载/填充(并管理实时更新)返回的实例。这需要订阅 WebSocket 以获取事件和 REST 调用以加载数据(包括重试的可能性)。
  • 服务通过调用实例上的方法(我的模式中的“回调”和“errback”)将其进度传递给实例。
  • 这些方法会更新 state 属性(即可观察)并可能更新其他内容(例如填充其值或填充可观察的错误消息)。
  • UI 询问 state observable 以向用户传达加载(和其他活动)的状态。

这在我的单个实例上效果很好。 state 方法被广泛询问(和更新)以确保有效操作。

我想在我的 KOArrays 上复制图案。这样,我可以区分尚未加载的空数组和恰好为空的已加载数组。我还可以在 KOArray 中添加 stateerror_message 以反映我的实例的行为。

【问题讨论】:

  • 您能否添加一个简短的描述,说明您究竟为什么需要这个?在大多数情况下,这听起来不像是一个合理的模式。

标签: typescript knockout.js extends


【解决方案1】:

总之,不可能像你描述的那样。

更长的故事

Knockout 不是以面向对象的方式实现的。所以基本上它公开的几乎所有东西都是函数或普通对象等。原型处理用于扩展由例如ko.observable返回的函数实例。所以基本上继承任何种类的淘汰赛的东西在 OOP 意义上是不可能的。

如果您需要继承来添加功能,您可以将新方法添加到ko.observableArray.fn 对象。然后这个函数将在之后的所有observableArray 实例上调用。这里有一个简单的例子。

ko.observableArray.fn.hasElements = function() {
  return this().length > 0;
}
...
if (myModel.selectedItems.hasElements()) {
  ...
}

如果你需要继承来限制某些东西,你可以像这样添加自己的工厂函数。

ko.myRestrictedObservableArray = function(initialValue) {
  if (!initialValue || !initialValue.length) {
    throw new "You can create an observable array only with a non-empty initial value";
  }

  return ko.observableArray(initialValue);
}

如果您愿意,您甚至可以覆盖原始函数,例如像这样。不过,在大多数情况下,这并不是一个好的做法。

var _original = ko.observableArray;
ko.observableArray = function(initialValue) {
  // ... do your custom things
  var array = _original.call(this, initialValue);
  // ... do your custom things 
  return array;
}

如果您还有其他要求,请在问题中更详细地描述。

【讨论】:

  • 如果您没有收到更新,我编辑了问题以添加示例。
【解决方案2】:

根据@Zoltan 的反馈,我最终选择了以下内容。我确信存在错误和优化机会,但它让我可以在 Typescript 类中编写所有内容(在两个方向进行适当的类型检查):

在纯 JS 中(例如 HTML 文件或单独的 JS 文件中的 <script>):

function RestCollection(initialValue) {
    const fn = ko.observableArray(initialValue);
    // Static properties
    ko.utils.extend(fn.constructor, RestCollectionMixin);
    // Methods (already constructed so must attach directly, not to prototype)
    const methods = Object.getOwnPropertyNames(RestCollectionMixin.prototype);
    for (let i=0; i<methods.length; i++) {
        if (methods[i] == "constructor") continue;  // don't move over the constructor
        fn[methods[i]] = RestCollectionMixin.prototype[methods[i]].bind(fn)
    }
    // Constructed properties
    ko.utils.extend(fn, new RestCollectionMixin());
    return fn;
} {}

// RestCollectionMixin needs this; don't return or it will break the constructor
function KnockoutObservableArrayClass() {}

在 TS 文件中:

// abstract is not passed to JS so this prevents misuse without breaking the RestCollection constructor
abstract class RestCollectionMixin<T> extends KnockoutObservableArrayClass<T> { // extends handles parent method access
    static FSM = class {
        static STATE_INIT = 'initializing';
        static STATE_READY = 'ready';
        static STATE_SAVING = 'save_requested';
        static STATE_DELETING = 'delete_requested';
        static STATE_DELETED = 'deleted';
        static STATE_ERROR = 'error';
    };
    public states() { // convenience accessor
        return this.constructor.FSM
    }
    public state:KnockoutObservable<string> = ko.observable(this.states().STATE_INIT);

    public processRestError(results) {
        this.state(this.states().STATE_ERROR)
        ...
    }
    ...
}

最后,RestCollectionKnockoutObservableArrayClass 的定义文件让 TS 检查工作:

// provide KnockoutObservableArray properties and methods to the Mixin
interface KnockoutObservableArrayClass<T> extends KnockoutObservableArray<T> {
    new<T>(initialValue): KnockoutObservableArray<T>
}
declare const KnockoutObservableArrayClass:KnockoutObservableArrayClass<any>;

// Define the focal class, capturing all extensions in RestCollectionMixin
interface RestCollection<T> extends RestCollectionMixin<T> {
    new<T>(initialValue): RestCollection<T>
}
declare const RestCollection:RestCollection<any>;

【讨论】:

    猜你喜欢
    • 2012-12-04
    • 1970-01-01
    • 2016-06-10
    • 2013-07-09
    • 2013-05-24
    • 2023-03-03
    • 2016-08-14
    • 2019-08-20
    • 1970-01-01
    相关资源
    最近更新 更多