简单,干净的方式来同步来自不同视图模型的可观察对象

Mic*_*est 5 data-binding observer-pattern knockout.js

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

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

function site2Model(username) = {
    this.username = ko.observable(username);
    ....
}
Run Code Online (Sandbox Code Playgroud)

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

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
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

  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
    }
    
    Run Code Online (Sandbox Code Playgroud)

    只要变化总是通过计算得出,这将使值保持同步.但是如果直接改变基础可观察量,它就不会这样做.

  2. 使用该subscribe方法从另一个更新每个:

    site3Model.site1.username.subscribe(function(value) {
        this.site2.username(value);
    }, site3Model);
    site3Model.site2.username.subscribe(function(value) {
        this.site1.username(value);
    }, site3Model);
    
    Run Code Online (Sandbox Code Playgroud)

    只要observable在值相同时抑制通知,这就可以工作; 否则你最终会得到一个无限循环.你也可以做检查早期: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);
    
    Run Code Online (Sandbox Code Playgroud)

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

由于我觉得所有这些方法都有缺点,是否有另一种方法可以做到这一点很简单(少于10行代码),高效(不运行不必要的代码或更新),以及灵活(处理多个级别的可观察量) )?

RP *_*yer 7

它不完全是10行代码(虽然您可以根据自己的喜好将其删除),但我在视图模型之间使用pub/sub消息来处理这种情况.

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

基本思想是创建一个ko.subscribable并使用基于主题的订阅.该库扩展了可订阅的add subscribeTo,publishOn以及syncWith(发布和订阅主题).这些方法将为observable设置正确的订阅,以自动参与此消息传递并与主题保持同步.

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

就像我说的那样你可以把它剥离到少于10行的代码.该库只增加了一些额外功能,例如能够取消订阅,能够控制发布实际发生的时间(equalityComparer),并且您可以指定转换以对传入值运行.

随意发布任何反馈.

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


Mic*_*est 5

瑞恩和约翰,谢谢你们的回答.不幸的是,我真的不想引入pub/sub系统所需的全局命名系统.

瑞恩,我同意这种subscribe方法可能是最好的.我已经整理了一组函数来处理订阅.我没有使用扩展,因为我也想处理observable本身可能是动态的情况.这些函数接受可观察对象或返回可观察对象的函数.如果source observable是动态的,我将accessor函数调用包装在一个计算的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);
}
Run Code Online (Sandbox Code Playgroud)

这大概是20行,所以也许我的目标不到10行有点不合理.:-)

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