大规模的KnockoutJS应用程序架构

Rya*_*yan 20 javascript asp.net-mvc-3 knockout-2.0 knockout.js

我喜欢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));
Run Code Online (Sandbox Code Playgroud)

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

思考?

编辑

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

ROF*_*IME 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
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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);

    }
Run Code Online (Sandbox Code Playgroud)

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

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

我使用Visual Studio"Web Essentials"扩展,允许您创建"捆绑"的js文件.(选择js文件,右键单击它们并转到Web Essentials - >创建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>
Run Code Online (Sandbox Code Playgroud)

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

header.js:

//put all views and view models in this
$(document).ready(function()
{
Run Code Online (Sandbox Code Playgroud)

footer.js:

//ends the jquery on document ready function
});
Run Code Online (Sandbox Code Playgroud)

5.分离您的HTML内容.

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

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

<!-- Begin Messaging -->    
    <!--#include virtual="Content/messaging.html" -->
<!-- End Messaging -->
Run Code Online (Sandbox Code Playgroud)

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

加载HTML内容异步:(如果用户使用的应用程序不太频繁,我会使用此内容)

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

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

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

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

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

<button data-bind="click: app1.load">Load App 1</button>
Run Code Online (Sandbox Code Playgroud)

哪个会有这个功能:

vm.visibility.set("app1");
Run Code Online (Sandbox Code Playgroud)

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


Bri*_*gel 7

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

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

foreach绑定

有约束力

绑定上下文

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

  • 这是一个非常简单的小提琴.http://jsfiddle.net/bczengel/fMsxc/正如你所看到的,我有一个主视图模型.Person和Child的每个实例也充当它自己的视图模型,在`foreach`绑定中你编写标记就好像你已经完成了一个单独的`applyBindings`.但您也可以通过绑定上下文访问父视图模型. (3认同)