以为我了解 Knockout.js,现在我不太确定

Posted

技术标签:

【中文标题】以为我了解 Knockout.js,现在我不太确定【英文标题】:Thought I understood Knockout.js, now I'm not so sure 【发布时间】:2017-10-09 18:18:35 【问题描述】:

很不确定我以前是否理解,现在不理解,或者我不理解,现在我开始......或者我不理解,现在仍然不理解。

我正在构建一个允许用户预约的应用。

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

屏幕 2:选择可用的约会时间(表格、确认按钮并继续前进)

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

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

屏幕 5:显示确认码

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

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

这看起来很疯狂。

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

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

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

任何帮助将不胜感激。

【问题讨论】:

模型和视图模型在淘汰赛中模糊。不要担心坚持某种模式——模式的存在是为了为你服务,而不是相反。我发现 UI 规定了什么是,什么不是,应该是 viewmodel-y。如果我得到一个 pojso 并且只需要在 UI 中显示它,它就是一个模型。如果它有一个需要交互式绑定的属性,我将它转换为一个 observable。不需要从集合中推送或弹出?这是一个数组。 UI必须响应?这是一个 observableArray。 【参考方案1】:

关于 observables 很酷的一点是你可以在其他对象中使用它们:

self.selectedLocation = self.model.selectedLocation;

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

【讨论】:

谢谢!我决定直接绑定到 model.selectedLocation。【参考方案2】:

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

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

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

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 文件中,但在您为您定义的全局命名空间下应用程序。

【讨论】:

以上是关于以为我了解 Knockout.js,现在我不太确定的主要内容,如果未能解决你的问题,请参考以下文章

参考和输出参数问题 [重复]

Knockout.JS:“删除”请求

Knockout JS特殊字符验证

Knockout.js - 封装视图模型并从外部隐藏它们

Knockout JS 如何将数据绑定到静态表单元素

knockout.js中的队列计时器