5

假设我有两个视图模型,每个模型都有一个可观察的属性,代表不同但相似的数据。

function site1Model(username) {
    this.username = ko.observable(username);
    ....
}

function site2Model(username) = {
    this.username = ko.observable(username);
    ....
}

这些视图模型是独立的,不一定相互链接,但在某些情况下,第三个视图模型会在它们之间创建链接。

function site3Model(username) = {
    this.site1 = new site1Model(username);
    this.site2 = new site2Model(username);
    // we now need to ensure that the usernames are kept the same between site1/2
    ...
}

以下是我提出的一些选项。

  1. 使用一个计算的 observable 读取一个并写入两个:

    site3Model.username = ko.computed({
        read: function() {
            return this.site1.username();    // assume they are always the same
        },
        write: function(value) {
            this.site1.username(value);
            this.site2.username(value);
        },
        owner: site3Model
    }
    

    只要更改始终通过计算,这将使值保持同步。但是如果一个底层的 observable 被直接改变了,它就不会这样做。

  2. 使用该subscribe方法相互更新:

    site3Model.site1.username.subscribe(function(value) {
        this.site2.username(value);
    }, site3Model);
    site3Model.site2.username.subscribe(function(value) {
        this.site1.username(value);
    }, site3Model);
    

    只要值相同时可观察对象抑制通知,这就会起作用;否则你最终会陷入无限循环。您也可以更早地进行检查:if (this.site1.username() !== value) this.site1.username(value);这还有一个问题,即可观察对象必须简单(如果它们本身是可观察对象,它将无法正常工作site1site2

  3. 用于computed进行订阅和更新:

    site3Model.username1Updater = ko.computed(function() {
        this.site1.username(this.site2.username());
    }, site3Model);
    site3Model.username2Updater = ko.computed(function() {
        this.site2.username(this.site1.username());
    }, site3Model);
    

    这种格式允许我们拥有其他依赖项。例如,我们可以 makesite1site2observables 然后使用this.site1().username(this.site2().username());这种方法还需要检查相等性以避免无限循环。如果我们不能依赖 observable 来做到这一点,我们可以在计算中检查,但会在我们正在更新的 observable 上添加另一个依赖项(直到类似observable.peek的东西可用)。这种方法也有一个缺点,就是最初运行一次更新代码来设置依赖关系(因为这就是computed工作方式)。

由于我觉得所有这些方法都有缺点,有没有另一种方法可以简单(少于 10 行代码)、高效(不运行不必要的代码或更新)和灵活(处理多个级别的 observables )?

4

4 回答 4

7

它并不完全是 10 行代码(尽管您可以根据自己的喜好对其进行精简),但对于这种情况,我在视图模型之间使用 pub/sub 消息。

这是我为它编写的一个小型库:https ://github.com/rniemeyer/knockout-postbox

基本思想只是创建ko.subscribable和使用基于主题的订阅。该库将订阅扩展为添加subscribeTo和(发布publishOnsyncWith订阅主题)。这些方法将为可观察对象设置正确的订阅,以自动参与此消息传递并与主题保持同步。

现在您的视图模型不需要相互直接引用,并且可以通过 pubsub 系统进行通信。您可以在不破坏任何内容的情况下重构您的视图模型。

就像我说的那样,您可以将其精简到不到 10 行代码。该库只是添加了一些额外功能,例如能够取消订阅,能够控制发布实际发生的时间(equalityComparer),并且您可以指定一个转换以对传入的值运行。

随时发布任何反馈。

这是一个基本示例:http: //jsfiddle.net/rniemeyer/mg3hj/

于 2012-05-30T02:58:54.390 回答
5

瑞安和约翰,谢谢你们的回答。不幸的是,我真的不想引入发布/订阅系统所需的全局命名系统。

瑞安,我同意这种subscribe方法可能是最好的。我已经组合了一组函数来处理订阅。我没有使用扩展,因为我还想处理可观察对象本身可能是动态的情况。这些函数接受可观察对象或返回可观察对象的函数。如果源 observable 是动态的,我将访问器函数调用包装在一个计算的 observable 中,以便拥有一个固定的 observable 来订阅。

function subscribeObservables(source, target, dontSetInitially) {
    var sourceObservable = ko.isObservable(source) 
            ? source 
            : ko.computed(function(){ return source()(); }),
        isTargetObservable = ko.isObservable(target),
        callback = function(value) {
            var targetObservable = isTargetObservable ? target : target(); 
            if (targetObservable() !== value)
                targetObservable(value);
        };
    if (!dontSetInitially)
        callback(sourceObservable());
    return sourceObservable.subscribe(callback);
}

function syncObservables(primary, secondary) {
    subscribeObservables(primary, secondary);
    subscribeObservables(secondary, primary, true);
}

这大约是 20 行,所以我的少于 10 行的目标可能有点不合理。:-)

我修改了 Ryan 的邮箱示例来演示上述功能:http: //jsfiddle.net/mbest/vcLFt/

于 2012-05-30T21:38:21.013 回答
1

另一种选择是创建一个隔离的数据上下文来维护可观察的模型。视图模型都在数据上下文中查找它们的数据并引用相同的对象,因此当一个更新时,它们都会这样做。VM 依赖于数据上下文,但不依赖于其他 VM。我最近一直在这样做,而且效果很好。虽然,它比使用 pub/sub 复杂得多。

如果您想要简单的发布/订阅,您可以使用他提到的 Ryan Niemyer 的库或使用内置发布/订阅消息传递(基本上是信使或事件聚合器)的 amplify.js。两者都是轻量级且解耦的。

于 2012-05-30T14:56:58.937 回答
0

以防有人需要。另一种选择是创建参考对象/可观察对象。这也处理包含多个可观察对象的对象。

(function(){
    var subscriptions = [];

    ko.helper = {
        syncObject: function (topic, obj) {
            if(subscriptions[topic]){
                return subscriptions[topic];
            } else {
                return subscriptions[topic] = obj;
            }
        }
    };
})();

在您的视图模型中。

function site1Model(username) {
    this.username = syncObject('username', ko.observable());
    this.username(username);
    ....
}

function site2Model(username) = {
    this.username = syncObject('username', ko.observable());
    this.username(username);
    ....
}
于 2016-03-08T07:41:19.710 回答