大规模的KnockoutJS应用程序架构

时间:2012-05-14 18:06:49

标签: javascript asp.net-mvc-3 knockout.js knockout-2.0

我喜欢KnockoutJS,但一直在努力找出用它构建大规模Javascript应用程序的最佳方法。

现在,我处理代码的方式是使用根视图模型构建,该模型通常从母版页级别开始,然后在其上进行扩展。我只在主视图上ko.applyBindings()。这是我的示例代码:

var companyNamespace = {};

// Master page. (a.k.a _Layout.cshtml)
(function(masterModule, $, ko, window, document, undefined) {
    var private = "test";

    masterModule.somePublicMethod = function() {};
    masterModule.viewModel = function() {
        this.stuff = ko.observable();
    };
}(companyNamespace.masterModule = companyNamespace.masterModule || {}, jQuery, ko, window, document));

// Index.cshtml.
(function(subModule, $, ko, window, document, undefined) {
    var private = "test";

    subModule.somePublicMethod = function() {};
    subModule.viewModel = function() {
        this.stuff = ko.observable();
    };

    $(document).ready(function() {
        ko.applyBindings(companyNamespace.masterModule);
    });
}(companyNamespace.masterModule.subModule = companyNamespace.masterModule.subModule || {}, jQuery, ko, window, document));

我只是担心,因为这是一个树形结构,如果我需要插入一个双母版页或类似的东西,这将是非常麻烦的重新分解。

思想?

修改

我知道你可以将绑定应用于单独的元素来改变绑定的范围,但是如果我有嵌套的视图模型呢?

2 个答案:

答案 0 :(得分:44)

我有一个相当大的knockout.js单页应用程序。 (当前20K +代码行),任何人都可以轻松维护和添加其他部分。我有数百个可观测量,性能仍然很好,即使在旧的iPod touch等移动设备上也是如此。它基本上是一个托管一套工具的应用程序。以下是我使用的应用程序的一些见解:

1。只有一个视图模型。它让事情变得简单恕我直言。

视图模型处理任何单个页面应用程序的基础知识,例如每个页面(app)的可见性,导航,错误,加载和Toast对话框等。视图模型的示例片段:(我将其分开甚至更进一步的js文件,但这是为了让你概述它的样子)

var vm = {

    error:
    {
        handle: function (error, status)
        {
           //Handle error for user here
        }
    },
    visibility:
    {
        set: function (page)
        {
            //sets visibility for given page
        }
    },
    permissions:
    {
        permission1: ko.observable(false),
        permission2: ko.observable(false)
        //if you had page specific permissions, you may consider this global permissions and have a separate permissions section under each app
    },
    loadDialog:
    {
        message: ko.observable(''),
        show: function (message)
        {
            //shows a loading dialog to user (set when page starts loading)
        },
        hide: function()
        {
            //hides the loading dialog from user (set when page finished loading)
        }
    },

    app1:
    {
        visible: ko.observable(false),
        load: function () 
        {
          //load html content, set visibility, app specific stuff here
        }
    },
    app2: 
    {
        visible: ko.observable(false),
        load: function () 
        {
          //load html content, set visibility, app specific stuff here
        }
    }

}

2.所有模型都进入单独的.js文件。

我将模型视为类,所以他们真正做的只是存储变量并具有一些基本的格式化函数(我试着保持它们的简单)。示例模型:

    //Message Class
    function Message {
        var self = this;

        self.id = ko.observable(data.id);
        self.subject = ko.observable(data.subject);
        self.body = ko.observable(data.body);
        self.from = ko.observable(data.from);

    }

3. 将AJAX数据库调用保存在自己的js文件中。

最好由部分或“app”分隔。例如,您的文件夹树可能是js / database /,其中app1.js和app2.js是js文件,包含基本的创建检索,更新和删除功能。示例数据库调用:

vm.getMessagesByUserId = function ()
{

    $.ajax({
        type: "POST",
        url: vm.serviceUrl + "GetMessagesByUserId", //Just a default WCF url
        data: {}, //userId is stored on server side, no need to pass in one as that could open up a security vulnerability
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        cache: false,
        success: function (data, success, xhr)
        {
            vm.messaging.sent.messagesLoaded(true);

            for (var i = 0; i < data.messages.length; i++)
            {
                var message = new Message({
                    id: data.messages[i].id,
                    subject: data.messages[i].subject,
                    from: data.messages[i].from,
                    body: data.messages[i].body
                });
                vm.messaging.sent.messages.push(message);
            }
        },
        error: function (jqXHR)
        {
            vm.error.handle(jqXHR.getResponseHeader("error"), jqXHR.status);
        }
    });
    return true;
};

4。将所有模型,视图模型和数据库js文件合并并缩小为一个。

我使用Visual Studio“Web Essentials”扩展,允许您创建“捆绑”的js文件。 (选择js文件,右键单击它们并转到Web Essentials - &gt;创建Javascript捆绑文件)我的Bundle文件设置如下:

<?xml version="1.0" encoding="utf-8"?>
<bundle minify="true" runOnBuild="true">
  <!--The order of the <file> elements determines the order of them when bundled.-->

  <!-- Begin JS Bundling-->
  <file>js/header.js</file>


  <!-- Models -->

  <!-- App1 -->
  <file>js/models/app1/class1.js</file>
  <file>js/models/app1/class2.js</file>

  <!-- App2 -->
  <file>js/models/app2/class1.js</file>
  <file>js/models/app2/class2.js</file>

  <!-- View Models -->
  <file>js/viewModel.js</file>

  <!-- Database -->
  <file>js/database/app1.js</file>
  <file>js/database/app2.js</file>

  <!-- End JS Bundling -->
  <file>js/footer.js</file>

</bundle>

header.js和footer.js只是文档就绪函数的包装器:

<强> header.js:

//put all views and view models in this
$(document).ready(function()
{

<强> footer.js:

//ends the jquery on document ready function
});

5。分离您的HTML内容。

不要保留一个难以浏览的大怪异html文件。由于敲除和HTTP协议的无状态的绑定,你可以很容易地进入淘汰陷阱。但是,我使用两个选项进行分离,这取决于我是否将该作品视为是否被用户大量访问:

服务器端包括:(只是指向另一个html文件的指针。如果我觉得这个应用程序的一部分被用户大量使用,我想使用它,但我想将它保持分开)

<!-- Begin Messaging -->    
    <!--#include virtual="Content/messaging.html" -->
<!-- End Messaging -->

您不希望使用服务器端包含太多,否则用户每次访问页面时必须加载的HTML量将变得相当大。话虽如此,这是迄今为止最容易分离你的html的解决方案,同时保持你的淘汰赛绑定。

加载HTML内容async:(如果用户使用的特定部分不太频繁,我会使用此内容)

我使用jQuery加载函数来完成此任务:

        // #messaging is a div that wraps all the html of the messaging section of the app
        $('#messaging').load('Content/messaging.html', function ()
        {
            ko.applyBindings(vm, $(this)[0]); //grabs any ko bindings from that html page and applies it to our current view model
        });

6。保持页面/应用的可见性可管理

显示和隐藏你的knockout.js应用程序的不同部分很容易发疯,因为你需要设置这么多不同的on和off开关,这些代码很难管理和记忆。首先,我将每个页面或应用程序保存在自己的“div”中(并在其自己的html文件中进行分离)。示例HTML:

<!-- Begin App 1 -->

<div data-bind="visible: app1.visible()">
<!-- Main app functionality here (perhaps splash screen, load, or whatever -->
</div>

<div data-bind="visible: app1.section1.visible()">
<!-- A branch off of app1 -->
</div>

<div data-bind="visible: app1.section2.visible()">
<!-- Another branch off of app1 -->
</div>

<!-- End App 1 -->


<!-- Begin App 2 -->
<div data-bind="visible: app2.visible()">
<!-- Main app functionality here (perhaps splash screen, load, or whatever -->
</div>
<!-- End App 2 -->

其次,我会有一个类似于此的可见性功能,它可以为您网站上的所有内容设置可见性:(它还可以在子功能中处理我的导航)

vm.visibility:
{
    set: function (page)
    {
      vm.app1.visible(page === "app1");
      vm.app1.section1.visible(page === "app1section1");
      vm.app1.section2.visible(page === "app1section2");
      vm.app2.visible(page === "app2");     
    }
};

然后只需调用app或page的加载函数:

<button data-bind="click: app1.load">Load App 1</button>

其中有这个功能:

vm.visibility.set("app1");

这应该涵盖大型单页面应用程序的基础知识。可能有比我提出的解决方案更好的解决方案,但这并不是一个糟糕的方法。多个开发人员可以轻松地在应用程序的不同部分工作,而不会与版本控制发生冲突,而不会发生冲突。

答案 1 :(得分:7)

我喜欢使用原型继承来设置我的视图模型。像你一样,我有一个“主”视图模型。该视图模型包含其他视图模型的实例或视图模型的可观察数组,您可以在标记中使用“foreach”和“with”绑定。在“foreach”和“with”绑定中,您可以使用$ data,$ parent,$ parents和$ root绑定上下文来引用您的父视图模型。

以下是KO文档中的相关文章。

foreach binding

with binding

binding context

如果你想我可以把小提琴放在一起。让我知道。