Крупномасштабная архитектура приложения KnockoutJS

Мне нравится 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));

Я просто беспокоюсь, поскольку это древовидная структура, и если бы мне нужно было вставить двойную главную страницу или что-то в этом роде, это было бы очень обременительно для рефакторинга.

Мысли?

ИЗМЕНИТЬ

Я знаю, что вы можете применять привязки к отдельным элементам, чтобы изменить область привязок, но что, если у меня есть вложенные модели представлений?


person Ryan    schedule 14.05.2012    source источник
comment
Знаете ли вы, что можете применять привязку к определенным элементам и делать несколько вызовов? Почему бы не хранить разные модели представления отдельно?   -  person Kyeotic    schedule 14.05.2012
comment
@Tyrsius - да, я знаю об этом и просто наткнулся на этот ответ: stackoverflow.com/questions/7963948/ однако что, если у меня есть вложенные модели представлений? Я обновлю свой ответ более подробно   -  person Ryan    schedule 14.05.2012
comment
Пока не смотрел эту презентацию, но она сделана никем иным, как Стивом Сандерсоном, автором книги Knockout, и называется она «Архитектура больших одностраничных приложений с помощью Knockout.js», так что она должна быть уместной: blog.stevensanderson.com/2014/06/11/< /а>   -  person mg1075    schedule 12.06.2014


Ответы (2)


Мне нравится настраивать свои модели представлений с использованием прототипного наследования. Как и у вас, у меня есть «основная» модель представления. Эта модель представления содержит экземпляры других моделей представлений или наблюдаемые массивы моделей представлений, из которых вы можете использовать привязки foreach и with в своей разметке. Внутри ваших привязок foreach и with вы можете использовать контексты привязки $data, $parent, $parents и $root для ссылки на модели родительского представления.

Вот соответствующие статьи в документации KO.

привязка foreach

с привязкой

контекст привязки

Если хочешь, я могу собрать скрипку. Дай мне знать.

person Brian Zengel    schedule 19.05.2012
comment
Вот очень простая скрипка. jsfiddle.net/bczengel/fMsxc Как видите, у меня есть модель основного представления. Каждый экземпляр Person и Child также действует как собственная модель представления, и внутри привязок foreach вы пишете свою разметку, как если бы вы сделали отдельный applyBindings. Но у вас также есть доступ к родительским моделям представлений через контексты привязки. - person Brian Zengel; 21.05.2012
comment
Кстати, я знаю, что перспектива ручного создания конструкторов для каждого объекта может показаться большой задачей. Но именно здесь может помочь плагин нокаут-мэппинга. knockoutjs.com/documentation/plugins-mapping.html . Я подумал, что в контексте этого вопроса вам будет полезнее увидеть, как это делается вручную. - person Brian Zengel; 21.05.2012

У меня есть довольно большое одностраничное приложение Knockout.js. (в настоящее время более 20 тысяч строк кода), который очень легко поддерживать и добавлять в него дополнительные разделы. У меня есть сотни наблюдаемых, и производительность по-прежнему отличная, даже на мобильных устройствах, таких как старый 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-файлы вашей модели, модели представления и базы данных в один.

Я использую расширение Visual Studio «Web Essentials», которое позволяет создавать «связанные» js-файлы. (Выберите файлы js, щелкните их правой кнопкой мыши и перейдите в Web Essentials -> Создать файл пакета Javascript). Мой файл пакета настроен следующим образом:

<?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: (я использую это, если данная часть приложения используется пользователями реже)

Для этого я использую функцию загрузки 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");

Это должно охватывать основы большого одностраничного приложения. Вероятно, есть лучшие решения, чем то, что я представил, но это неплохой способ сделать это. Несколько разработчиков могут легко работать над разными разделами приложения без конфликтов с контролем версий и так далее.

person Gaff    schedule 12.03.2013
comment
+1 Мне очень нравится идея разделения содержимого HTML на частичные части с помощью #include. Хотя я не согласен с вами в одном: я предпочитаю иметь отдельные модели просмотра для каждой страницы/раздела. - person Cory House; 19.04.2013
comment
Первоклассный, превосходный пример из реального мира, здорово, как вы все разделяете, потрясающие вещи - person saj; 01.06.2014