我是不是总是使用 Knockout 映射插件来执行我的视图模型,从而过度使用它?

Posted

技术标签:

【中文标题】我是不是总是使用 Knockout 映射插件来执行我的视图模型,从而过度使用它?【英文标题】:Am I overusing the Knockout mapping plugin by always using it to do my viewmodel?我是否总是使用 Knockout 映射插件来执行我的视图模型,从而过度使用它? 【发布时间】:2011-11-21 05:44:29 【问题描述】:

我仍在学习 Knockout 的正确用法,并且我发现自己在设置我的视图模型时很快就摆脱了输入 ko.observable 的麻烦,而只是定义一个对象文字并通过映射插件传递它,例如

var viewModel = ko.mapping.fromJS(data);

或者至少,类似于将我的所有数据填充到 viewModel 上的一个属性中,就像这样

var viewModel =  
    ... events etc ... , 
    "data": ko.mapping.fromJS(data)

说实话,我这样做的主要原因是为了避免重复输入ko.observableko.observableArray。我只是想弄清楚这是否是一种好方法,以及将特定的 var x = ko.observable() 声明全部删除是否有任何缺点。此外,我在加载时执行此操作,而不是响应任何 ajax 调用等,据我所知,这就是映射插件的设计目的。

在你的敲除工作中,你是否仍然手动地、一个一个地声明 observables,或者你是否使用了我使用的 mapping.fromJS 方法?像这样频繁使用映射插件有什么具体的缺点吗?

编辑:

具体例子

In this article,Steve 通过这样做来设置他的 viewModel

var initialData = [  ...  ,  ...  ]; // json from the serializer
var viewModel = 
    gifts : ko.observableArray(initialData)
;

通常情况下,我也会在这种情况下使用ko.mapping.fromJS,特别是要确保数组中的对象也被转换为可观察对象。看看他做了什么,我的方法似乎有点矫枉过正,并增加了一些不必要的开销。

【问题讨论】:

您可能需要考虑的一个问题是映射是否会在视图和数据模型之间创建紧密耦合。还有是否需要扩展器。这些可以使用映射选项添加,但如果您要扩展大部分可观察对象,则可能会变得笨拙,例如表单验证时可能发生的情况。 【参考方案1】:

在使用 Knockout 一段时间后,我注意到映射插件有一些额外的选项,可以让您对映射过程进行更精细的控制。

控制生成的属性类型和数量

有几种方法可以实现这一点,我将介绍一些方法,但最终结果是您最终会从映射插件中获得更轻的结果,因为一切都不可观察。

基本上,您将所有您认为不会改变的东西都保留为正常属性,并且只从您想要观察的特定项目中制作可观察对象。

使mapping 省略某些属性

您可以通过指定 ignoreinclude 等内容,使映射插件从最终结果中完全省略属性。这两者都完成了同样的事情,只是以相反的方式。

注意:示例来自knockout.js mapping plugin documentation,我添加的cmets

映射插件参数:include

以下 sn-p 将忽略源对象中的所有属性,而不是通过 include 参数传入的属性。

// specify the specific properties to include as observables in the end result 
var mapping = 
    // only include these two properties
    'include': ["propertyToInclude", "alsoIncludeThis"]


// viewModel will now only contain the two properties listed above, 
//    and they will be observable
var viewModel = ko.mapping.fromJS(data, mapping);

映射插件参数:ignore

如果您只想省略源对象的某些属性,请使用ignore 参数,如下所示。它将从源对象中除指定属性之外的所有属性中生成可观察对象。

// specify the specific properties to omit from the result, 
//    all others will be made observable
var mapping = 
    // only ignore these two properties
    'ignore': ["propertyToIgnore", "alsoIgnoreThis"]


// viewModel will now omit the two properties listed above, 
//    everything else will be included and they will be an observable
var viewModel = ko.mapping.fromJS(data, mapping);

控制哪些属性可观察或不可观察

如果您需要包含属性,但您认为不需要将它们设为可观察(无论出于何种原因),映射插件可以提供帮助。

映射插件参数:copy

如果您希望映射插件简单地复制普通属性而不使它们可观察,请使用此参数,如下所示。

// tell the mapping plugin to handle all other properties normally, 
//    but to simply copy this property instead of making it observable
var mapping = 
    'copy': ["propertyToCopy"]

var viewModel = ko.mapping.fromJS(data, mapping);

完全控制映射过程

如果您希望 100% 控制在映射过程中创建的内容,包括在对象中放置闭包和订阅的能力,那么您希望使用“创建”选项。

具有计算属性的简单结果

这是一个示例,我将 ajax 调用中的数据映射到具有 results 属性的对象。我不想要任何可观察的东西,我只想要一个简单的生成属性,该属性将由对象上的其他简单属性组成。也许不是最引人注目的例子,但它展示了功能。

var searchMappingConfig = 
    // specific configuration for mapping the results property
    "results": 
                    // specific function to use to create the items in the results array
        "create": function (options) 
            // return a new function so we can have the proper scope/value for "this", below
            return new function () 

                // instead of mapping like we normally would: ko.mapping.fromJS(options.data, , this);
                // map via extend, this will just copy the properties from the returned json element to "this"
                // we'll do this for a more light weight vm since every last property will just be a plain old property instead of observable
                $.extend(this, options.data);

                // all this to add a vehicle title to each item
                this.vehicleTitle = this.Year + "<br />" + this.Make + " " + this.Model;
                , this);
            ;
        
    

订阅、关闭和映射,哦,我的

另一种情况是,如果您希望结果中有闭包和订阅。此示例太长,无法完整包含,但它适用于车辆制造商/型号层次结构。如果模型未启用,我希望给定品牌(父)的所有模型(子)都未启用,并且我希望通过订阅来完成。

// here we are specifying the way that items in the make array are created, 
//    since makes has a child array (Models), we will specify the way that 
//    items are created for that as well
var makesModelsMappingConfig = 
   // function that has the configuration for creating makes
   "create": function (options) 
      // return a new function so we can have the proper 
      //    scope/value for "this", below
      return new function () 

         // Note: we have a parent / child relationship here, makes have models. In the 
         //    UI we are selecting makes and then using that to allow the user to select 
         //    models. Because of this, there is going to be some special logic in here 
         //    so that all the child models under a given make, will automatically 
         //    unselect if the user unselects the parent make.

         // make the selected property a private variable so it can be closure'd over
         var makeIsSelected = ko.protectedComputed(false);

         // expose our property so we can bind in the UI
         this.isSelected = makeIsSelected;

         // ... misc other properties and events ...

         // now that we've described/configured how to create the makes, 
         //    describe/configure how to create the models under the makes
         ko.mapping.fromJS(options.data, 
            // specific configuration for the "Models" property                  
            "Models": 
               // function that has the configuration for creating items 
               //    under the Models property
               "create": function (model) 

                  // we'll create the isSelected as a local variable so 
                  //    that we can flip it in the subscription below, 
                  //    otherwise we wouldnt have access to flip it
                  var isSelected = ko.protectedComputed(false);

                  // subscribe to the parents "IsSelected" property so 
                  //    the models can select/unselect themselves
                  parentIsSelected.current.subscribe(function (value) 
                     // set the protected computed to the same 
                     //    value as its parent, note that this 
                     //    is just protected, not the actual value
                     isSelected(value);
                  );


                  // this object literal is what makes up each item 
                  //    in the Models observable array 
                  return 
                     // here we're returning our local variable so 
                     //    we can easily modify it in our subscription
                     "isSelected": isSelected,

                     // ... misc properties to expose 
                     //     under the item in the Model array ...

                  ;
               
            
         , this);
      ;
   
;

总而言之,我发现你很少需要 100% 的对象来传递给插件,而且你很少需要 100% 的对象是可观察的。深入研究映射配置选项并创建各种复杂和简单的对象。我们的想法是只获得您需要的一切,不多也不少。

【讨论】:

你能解释一下 ko.protectedComputed 的作用吗?我在文档中找不到任何关于它的信息。还有,你如何初始化 parentIsSelected? 映射属性如 include 和 ignore 仅在 ko.mapping.toJS()... 复制和观察选项与 ko.mapping.fromJS() 相关,并让您控制如何将 JSON 数据映射到 ko viewModel...只是想确保理解,因为这是最初胜过我的细节之一。 @hisa_py parentIsSelected 只是一个受保护的计算,你可以在这里找到更多关于这些underwatergorilladome.com/… 这是我最近看到的最慷慨、最有用的答案之一。感谢您抽出宝贵时间。【参考方案2】:

我对您的建议与我刚刚在 https://***.com/questions/7499133/mapping-deeply-hierarchical-objects-to-custom-classes-using-knockout-mapping-plug. 回答的另一个问题相同

您使用地图插件的理由是合理的,我使用的就是这个。为什么输入的代码比你必须的多?

根据我的淘汰赛经验(全部 4 个月),我发现我手动执行的操作越少,让淘汰赛例程完成他们的工作,我的应用程序似乎运行得越好。我的建议是先尝试最简单的方法。如果它不能满足您的需求,请查看简单的方法是如何完成它的“事情”并确定必须更改哪些内容才能满足您的需求。

【讨论】:

【参考方案3】:

Allen,我最近对 ​​Knockout.js 的学习经历与您的相似。我们使用来自服务器的深层分层对象图,我已经定义了显式的可实例化视图模型函数,它保留了它的基本结构。

我首先将每个属性明确定义为相关视图模型上的可观察对象,但很快就失控了。此外,切换到使用映射插件的一个主要原因是,我们必须频繁地将图的 Ajax 发布到服务器,在服务器上它与持久版本合并,然后在服务器上以这样一种方式进行验证,使得许多属性可以更改和集合被修改,并作为 Ajax 结果返回一个新实例,它必须与客户端表示重新合并。这变得非常困难,并且映射插件通过允许标识符的规范来解决添加/删除/更新并将更新的图形重新映射到原始图形,从而大有帮助。

它还通过对子视图模型使用“创建”选项来帮助创建原始图形。在每个视图模型构造函数中,我都会收到对父视图模型的引用以及用于构建子视图模型的数据,然后创建更多映射选项以从传入的子数据创建孙子。

我最近发现的唯一(轻微)缺点,如this question 中所述,是在执行 ko.mapping.toJSON 时,它不会挂钩您可能在视图模型的原型中定义的任何 toJSON 覆盖为了从序列化中排除属性。正如 Ryan Niemeyer 在那篇文章中所建议的那样,我可以通过在取消映射中指定忽略选项来解决这个问题。

总之,我肯定会坚持使用映射插件。 Knockout.js 规则。

【讨论】:

这听起来很有趣,您应该在某个地方发布一些更深入的内容!你能想象在没有淘汰赛的情况下完成所有这些工作吗?哦,是的,我在使用 .toJSON 时遇到了同样的问题,这令人失望,但我也能够解决它。【参考方案4】:

knockout-data-projections 可能是一个更简单但很有帮助的插件

目前,它不处理 js 到 viewmodel 的映射,但它可以很好地处理 view model 到 JS 的映射。

【讨论】:

很好,这看起来与映射插件相反。有点像使用ko.toJS,但具有更全面的参数集。我对toJS 似乎没有采用mapping 插件采用的全套对象转换参数感到失望。 是的,它可以用于特定场景。

以上是关于我是不是总是使用 Knockout 映射插件来执行我的视图模型,从而过度使用它?的主要内容,如果未能解决你的问题,请参考以下文章

带有 require.js 的 Knockout.js 映射插件

Knockout.js 数据验证之插件版和无插件版

KnockoutJS 3.X API 第八章 映射(mapping)插件

Knockout.js 映射忽略

单选按钮的 knockout.js 布尔数据绑定问题

淘汰制映射插件 - 使所有对象可观察