20

我喜欢 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));

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

想法?

编辑

我知道您可以将绑定应用于单独的元素以更改绑定的范围但是如果我有嵌套的视图模型怎么办?

4

2 回答 2

44

我有一个相当大的 knockout.js 单页应用程序。(目前有 20K+ 行代码)任何人都可以很容易地维护和添加额外的部分。我有数百个 observables,性能仍然很好,即使在像旧 iPod touch 这样的移动设备上也是如此。它基本上是一个承载一套工具的应用程序。以下是对我使用的应用程序的一些见解:

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

视图模型处理任何单页应用程序的基础知识,例如每个页面(应用程序)的可见性、导航、错误、加载和吐司对话框等。 视图模型示例片段:(我将其分离出更进一步的 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文件中。

最好按部分或“应用程序”分隔。例如,您的文件夹树可能是 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 文件合并并缩小为一个。

我使用允许您创建“捆绑”js 文件的 Visual Studio“Web Essentials”扩展。(选择 js 文件,右键单击它们并转到 Web Essentials --> Create Javascript Bundle File)我的 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()
{

页脚.js:

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

5. 分离您的 HTML 内容。

不要保留一个难以浏览的巨大 html 文件。因为knockout的绑定和HTTP协议的无状态,很容易掉入knockout这个陷阱。但是,我使用两个选项进行分离,具体取决于我是否认为该作品被用户大量访问:

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

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

您不想使用服务器端包含太多,否则用户每次访问页面时必须加载的 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
        });

6.保持您的页面/应用程序的可见性易于管理

显示和隐藏您的 knockout.js 应用程序的不同部分很容易因为大量难以管理和记住的代码行而变得疯狂,因为您必须设置如此多不同的打开和关闭开关。首先,我将每个页面或应用程序保存在其自己的“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");     
    }
};

然后只需调用应用程序或页面的加载函数:

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

其中将具有此功能:

vm.visibility.set("app1");

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

于 2013-03-12T16:46:23.897 回答
7

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

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

foreach 绑定

带装订

绑定上下文

如果你愿意,我可以拼凑一个小提琴。让我知道。

于 2012-05-19T20:32:46.617 回答