以为我明白 Knockout.js,现在我不太确定

Thought I understood Knockout.js, now I'm not so sure

非常不确定我是否曾经理解现在不理解,或者我不理解现在我开始......或者我不理解现在仍然不理解。

我正在构建一个让用户可以预约的应用程序。

屏幕 1:Select 位置和服务类型(选项列表,按钮确认并继续)

屏幕 2:Select 可用的预约时间(table,用于确认并继续的按钮)

屏幕 3:输入姓名和联系信息(文本输入,按钮确认并继续)

屏幕 4:输入验证码(发短信给用户,按钮确认并继续)

屏幕 5:显示确认码

我正在重构,因为我意识到一切都在我的 ViewModel 中,但不确定什么属于我的模型以及什么属于我的 ViewModel。我只是将 selectedLocation(用户从列表中选择的项目)放在模型中,但我需要它可以被视图访问,所以我这样做了:

self.selectedLocation = ko.pureComputed({
    read: function() {
        return self.model.selectedLocation();
    },
    write: function(value){
        self.model.selectedLocation(value);
    }
});

这看起来很疯狂。

这应该只在 ViewModel 中吗?我是否应该在模型中有一个 selectedLocation 变量,在 ViewModel 中有一个 currentlySelectedLocation,然后在用户确认位置和服务类型时更新 model.selectedLocation?

每个单独的屏幕都应该有自己的视图模型吗?我正在使用单个 .html 文件并更新显示的内容,而不是逐页更新。

我确定我没有包含一些重要的信息,我很抱歉,很乐意回答任何问题。

如有任何帮助,我们将不胜感激。

Observable 最酷的地方在于您可以在其他对象中使用它们:

self.selectedLocation = self.model.selectedLocation;

但我建议直接绑定到 model.selectedLocation 也可以。在 Knockout 中,不需要 model/view-model 区分;一切都是视图模型。

最好将模型和视图模型分开。在我看来,模型是您正在使用的数据的表示,视图模型将各种模型组合在一起,并向 运行 页面逻辑添加类似控制器的功能(处理 UI 交互、获取数据、数据绑定等)。您的模型将只是一组属性,也许还有一些辅助属性,这些辅助属性可以执行诸如从模型的原始数据中提供格式化显示文本之类的事情。但是模型中不会有任何真正的逻辑。如果您需要模型属性本身的双向数据绑定,那么您只需将它们定义为 KO observable。

鉴于您希望在一个 html 文件(基本上是一个 SPA)中完成所有这些工作,您肯定希望将其分解为几个 "viewmodels"。在这种情况下,我在 "viewmodels" 周围加上引号,因为 Knockout 只能将绑定应用于单个 viewmodel 对象,但是您可以将 Javascript 对象与 KO observables n 级深度嵌套,并且 KO 可以遵循对象图(如在 Michael Best 的回答中说明)。我们称这些嵌套的 "viewmodels" 子视图模型。

我建议创建一个代表整个应用程序的主视图模型,并为应用程序的每个页面至少创建一个子视图模型。所以它可能看起来像这样:

var AppViewModel = function (screen1Vm, screen2Vm, ...) {
    var self = this;

    self.screen1ViewModel = screen1Vm;
    self.screen2ViewModel = screen2Vm;
    ...
}

var Screen1ViewModel = function () {
    var self = this;

    self.selectedLocation = ko.observable(null); //Initialize as null
    self.locations = ko.observableArray([]); //assuming they're selecting from a list of Location objects.  Initialize as empty array.
    ...
    //More logic to handle UI events, like a KO click event binding when one of the locations is selected, which is where self.selectedLocation would get set.  Something like this
    self.locationSelected = function (location) {
        //When bound to a control inside a KO foreach context, the actual object in the KO observable array will be passed in.
        self.selectedLocation(location);
    }
}

var Location = function (description, city, state, ...) {
    var self = this;

    self.description = ko.observable(description);
    self.city = ko.observable(city);
    self.state = ko.observable(state);
    ...
}

...

var screen1Vm = new Screen1ViewModel();
var screen2Vm = new Screen2ViewModel();
...
var myAppVm = new AppViewModel(screen1Vm, screen2Vm, ...)
ko.applyBindings(myAppVm);

然后你会有一些 HTML 看起来像这样(显然将 DOM 元素更改为视图所需的元素):

<div data-bind="foreach: screen1ViewModel.locations">
    <!--Bind the click event to the viewmodel handler, and display the description of the location in the button-->
    <button data-bind="click: $parent.locationSelected"><span data-bind="text: description"></span></button>
</div>

最后一点。由于您在一个页面中完成所有操作,我建议尽可能多地使用 KO 模板来组织 HTML,并将每个子视图模型放在它们自己的 .js 文件中,但在一个全局命名空间下你为你的应用程序定义。