→ Понятие и назначение одностраничного web портала spa. Single page application или одностраничное веб-приложение. Последовательность работы с веб приложением

Понятие и назначение одностраничного web портала spa. Single page application или одностраничное веб-приложение. Последовательность работы с веб приложением

Продукты и технологии:

Single-Page Applications (SPA), ASP.NET Web API, Knockout.js, Ember.js, AJAX и HTML5

В статье рассматриваются:

  • создание уровня сервисов и веб-клиента AJAX для приложения-примера;
  • шаблоны MVC и MVVM;
  • связывание с данными;
  • создание веб-клиента с применением Knockout.js;
  • создание веб-клиента с применением Ember.js.

Одностраничные приложения (Single-Page Applications, SPA) - это веб-приложения, которые загружают одну HTML-страницу и динамически обновляют ее при взаимодействии с пользователем.

SPA используют AJAX и HTML5 для создания гибких и адаптивных веб-приложений без постоянных перезагрузок страницы. Однако это означает, что большая часть работы возлагается на клиентскую сторону, а именно на JavaScript-код. Разработчику для традиционной ASP.NET может быть трудно совершить такой кульбит. К счастью, существует множество JavaScript-инфраструктур с открытым исходным кодом, которые облегчают создание SPA.

В этой статье я пошагово пройду процесс создания простого SPA-приложения. Попутно вы ознакомитесь с некоторыми фундаментальными концепциями создания SPA, в том числе с шаблонами Model-View-Controller (MVC) и Model-View-ViewModel (MVVM), связыванием с данными и маршрутизацией (routing).

О приложении-примере

Я создал приложение-пример для операций с простой базой данных по фильмам (рис. 1 ). В крайнем слева столбце страницы отображается список жанров. Выбор жанра приводит к появлению списка соответствующих фильмов. Кнопка Edit рядом с записью позволяет изменять эту запись. После редактирования можно щелкнуть кнопку Save для передачи обновления на сервер или кнопку Cancel для отмены изменений.

Рис. 1. SPA-приложение для базы данных по фильмам

Я создал две версии этого приложения: одна из них использует библиотеку Knockout.js, а другая - библиотеку Ember.js. Эти две библиотеки основаны на разных подходах, поэтому будет весьма поучительно сравнить их. В обоих случаях клиентское приложение не требовало более 150 строк JavaScript-кода. На серверной стороне я задействовал ASP.NET Web API, чтобы обслуживать JSON для клиента. Исходный код обеих версий вы найдете на github.com/MikeWasson/MoviesSPA .

(Примечание Я создавал приложение, используя RC-версию Visual Studio 2013. В RTM-версии некоторые вещи могли измениться, но они не должны повлиять на код.)

Обзор

В традиционном веб-приложении при каждом вызове сервера тот осуществляет рендеринг новой HTML-страницы. Это вызывает обновление страницы в браузере. Если вы когда-нибудь писали приложение Web Forms или PHP, этот жизненный цикл страниц должен быть знаком вам.

В SPA после загрузки первой страницы все взаимодействие с сервером происходит через AJAX-вызовы. Эти AJAX-вызовы возвращают данные (не разметку) - обычно в формате JSON. Приложение использует JSON-данные для динамического обновления страницы без ее перезагрузки. Рис. 2 иллюстрирует разницу между этими двумя подходами.


Рис. 2. Сравнение традиционного жизненного цикла страницы с жизненным циклом в SPA

Одно из преимуществ SPA очевидно: приложения более гибкие и адаптивные, свободные от рваного эффекта перезагрузки страницы и ее рендеринга заново. Другое преимущество может оказаться менее очевидным и касается того, как вы проектируете архитектуру веб-приложения. Отправка данных приложения как JSON обеспечивает разделение между презентационной частью (HTML-разметкой) и прикладной логикой (AJAX-запросы плюс JSON-ответы).

Это разделение упрощает проектирование и развитие каждого уровня. В SPA-приложении с тщательно продуманной архитектурой можно изменять HTML-разметку, не касаясь кода, который реализует прикладную логику (по крайней мере, в идеале). Вы увидите это на практике, когда мы будем обсуждать связывание с данными.

В чистом SPA все UI-взаимодействие происходит на клиентской стороне через JavaScript и CSS. После начальной загрузки страницы сервер действует исключительно как уровень сервисов. Клиенту нужно просто знать, какие HTTP-запросы он должен посылать. Ему не важно, как сервер реализует свою часть.

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

Создание проекта в Visual Studio

В Visual Studio 2013 есть один тип проекта ASP.NET Web Application. Мастер этого проекта позволяет выбрать ASP.NET-компоненты, которые будут включены в проект. Я начал с шаблона Empty, а затем добавил в проект ASP.NET Web API, установив флажок Web API в разделе Add folders and core references for, как показано на рис. 3 .


Рис. 3. Создание нового ASP.NET-проекта в Visual Studio 2013

В новом проекте есть все библиотеки, необходимые для Web API, а также кое-какой конфигурационный код Web API. Я не вводил никаких зависимостей от Web Forms или ASP.NET MVC.

Обратите внимание на рис. 3 , что Visual Studio 2013 включает шаблон Single Page Application. Этот шаблон устанавливает скелет SPA-приложения, основанный на Knockout.js. Он поддерживает вход с применением базы данных с информацией о членстве в группах или с помощью внешнего провайдера аутентификации. Я не стал использовать этот шаблон в своем приложении, потому что хотел показать более простой пример с нуля. Шаблон SPA - отличный ресурс, особенно если вам нужно добавить аутентификацию в приложение.

Создание уровня сервисов

Я использовал ASP.NET Web API, чтобы создать простой REST API для приложения. Не буду здесь вдаваться в детали Web API - подробности вы можете прочитать по ссылке asp.net/web-api.

Сначала я создал класс Movie, представляющий фильм. Этот класс делает две вещи:

  • сообщает Entity Framework (EF), как создавать таблицы базы данных для хранения информации о фильмах;
  • сообщает Web API, как форматировать полезные данные JSON.

Вы не обязаны использовать одну модель для обеих целей. Например, вам может понадобиться, чтобы схема базы данных отличалась от полезных данных JSON. В этом приложении я ничего не усложняю:

  • namespace MoviesSPA.Models
  • public class Movie
  • public int ID { get; set; }
  • public string Title { get; set; }
  • public int Year { get; set; }
  • public string Genre { get; set; }
  • public string Rating { get; set; }
  • Затем я воспользовался технологией scaffolding в Visual Studio для создания контроллера Web API, который задействует EF в качестве уровня данных. Чтобы применить эту технологию, щелкните правой кнопкой мыши папку Controllers в Solution Explorer и выберите Add / New Scaffolded Item. В мастере Add Scaffold укажите Web API 2 Controller with actions, using Entity Framework, как показано на рис. 4 .


    Рис. 4. Добавление контроллера Web API

    На рис. 5 приведен мастер Add Controller. Я присвоил контроллеру имя MoviesController. Имя имеет значение, так как URI для REST API основываются на имени контроллера. Я также установил флажок Use async controller actions, чтобы задействовать преимущества новой функциональности async в EF 6. Я выбрал класс Movie в качестве модели и указал New data context, чтобы создать новый контекст данных EF.


    Рис. 5. Мастер Add Controller

    Мастер добавляет два файла:

    • MoviesController.cs - определяет контроллер Web API, который реализует REST API для приложения;
    • MovieSPAContext.cs - это в основном склеивающий слой EF, который предоставляет методы для запроса нижележащей базы данных.

    В табл. 1 показан REST API по умолчанию, создаваемый технологией scaffolding.

    Табл. 1. REST API по умолчанию, созданный технологией scaffolding из Web API

    Значения в фигурных скобках являются заменителями для подстановки. Например, чтобы получить фильм с идентификатором, равным 5, URI должен выглядеть так: /api/movies/5.

    Я расширил этот API, добавив метод, который находит все фильмы указанного жанра:

    Клиент указывает жанр в строке запроса URI. Например, чтобы получить все фильмы жанра Drama, клиент посылает GET-запрос на /api/movies?genre=drama. Web API автоматически связывает параметр запроса с параметром genre в методе GetMoviesByGenre.

    Создание веб-клиента

    До сих пор я просто создавал REST API. Если вы отправите GET-запрос на /api/movies?genre=drama, исходный HTTP-ответ будет выглядеть так:

    HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; charset=utf-8 Date: Tue, 10 Sep 2013 15:20:59 GMT Content-Length: 240 [{"ID":5,"Title":"Forgotten Doors","Year":2009,"Genre":"Drama","Rating":"R"}, {"ID":6,"Title":"Blue Moon June","Year":1998,"Genre":"Drama","Rating":"PG-13"},{"ID":7,"Title":"The Edge of the Sun","Year":1977,"Genre":"Drama","Rating":"PG-13"}]

    Теперь мне нужно написать клиентское приложение, которое делает с этим что-то осмысленное. Базовый рабочий процесс такой:

    • UI инициирует AJAX-запрос;
    • обновляем HTML для отображения полезных данных ответа;
    • обрабатываем AJAX-ошибки.

    Вы могли закодировать все это вручную. Например, вот некоторый jQuery-код, который создает список названий фильмов:

  • $.getJSON(url)
  • .done(function (data) {
  • // При успехе data содержит список фильмов
  • var ul = $("")
  • $.each(data, function (key, item) {
  • // Добавляем элемент в список
  • $("
  • ", { text: item.Title }).appendTo(ul);
  • $("#movies").html(ul);
  • В этом коде есть кое-какие проблемы. Он смешивает прикладную логику с презентационной и тесно связан с вашим HTML. Кроме того, писать его весьма утомительно. Вместо того чтобы сосредоточиться на приложении, вы тратите время на написание обработчиков событий и кода, манипулирующего DOM.

    Решение заключается в том, чтобы использовать JavaScript-инфраструктуру. К счастью, их выбор довольно велик, и эти инфраструктуры имеют открытый исходный код. К некоторым из более популярных инфраструктур относятся Backbone, Angular, Ember, Knockout, Dojo и JavaScriptMVC. Большинство использует вариации шаблонов MVC или MVVM, поэтому будет полезно вкратце рассмотреть эти шаблоны.

    Шаблоны MVC и MVVM

    Корни шаблона MVC уходят в 80-е годы прошлого века и связаны с ранними графическими UI. Цель MVC - разбиение кода на три уровня со своими обязанностями (рис. 6 ). Вот что они делают:

    • модель представляет данные и бизнес-логику предметной области;
    • представление отображает модель;
    • контроллер принимает пользовательский ввод и обновляет модель.

    Рис. 6. Шаблон MVC

    Более современная вариация MVC - шаблон MVVM (рис. 7 ). В шаблоне MVVM:

    • модель по-прежнему представляет данные предметной области;
    • модель представления - это абстрактное отражение представления;
    • представление отображает модель представления и посылает пользовательский ввод модели представления.

    Рис. 7. Шаблон MVVM

    View Model View Model

    В JavaScript-инфраструктуре MVVM представлением является разметка, а моделью представления - код.

    MVC имеет много вариаций, а литература по MVC зачастую противоречива и запутана. По-видимому, это и не удивительно для проектировочного шаблона, который начал свою жизнь со Smalltalk-76 и все еще применяется в современных веб-приложениях. Поэтому, хоть и хорошо знать теорию, главное - понимать конкретную инфраструктуру MVC, используемую вами.

    Создание веб-клиента с применением Knockout.js

    Для первой версии своего приложения я использовал библиотеку Knockout.js. Knockout следует шаблону MVVM, соединяя представление и модель представления через связывание с данными.

    Чтобы создать привязки данных, вы добавляете к HTML-элементам специальный атрибут data-binding. Например, следующая разметка связывает элемент span со свойством genre в модели представления. Всякий раз, когда изменяется значение genre, Knockout автоматически обновляет HTML:

  • Привязки также могут работать в другом направлении, скажем, если пользователь вводит текст в поле, Knockout обновляет соответствующее свойство в модели представления.

    Удобно, что связывание с данными осуществляется декларативно. Вам не требуется подключать модель представления к элементам HTML-страницы. Просто добавьте атрибут data-binding, и Knockout сделает остальное.

    Я начал с создания HTML-страницы с базовой разметкой без связывания с данными, как показано на рис. 8 .

    Рис. 8. Начальная HTML-разметка

  • Movies SPA
  • TitleYearRating
  • No records found.

  • (Примечание Я использовал библиотеку Bootstrap для оформления внешнего вида приложения, поэтому в настоящем приложении уйма дополнительных элементов и CSS-классов, управляющих форматированием. Для ясности я убрал все это из кода.)

    Создание модели представления

    Наблюдаемые объекты (observables) занимают центральное место в системе связывания с данными в Knockout. Наблюдаемым является объект, который хранит какое-то значение и может уведомлять подписчиков об изменении этого значения. Следующий код преобразует JSON-представление фильма в эквивалентный объект с наблюдаемыми полями:

  • function movie(data) {
  • var self = this;
  • data = data // {};
  • // Данные из модели
  • self.ID = data.ID;
  • self.Title = ko.observable(data.Title);
  • self.Year = ko.observable(data.Year);
  • self.Rating = ko.observable(data.Rating);
  • self.Genre = ko.observable(data.Genre);
  • На рис. 9 показана начальная реализация модели представления. Эта версия поддерживает только получение списка фильмов. Средства редактирования я добавлю позже. Модель представления содержит наблюдаемые свойства для списка фильмов, строку ошибки и текущий жанр.

    Рис. 9. Модель представления

  • var ViewModel = function () {
  • var self = this;
  • // Наблюдаемые свойства модели представления
  • self.movies = ko.observableArray();
  • self.error = ko.observable();
  • // Жанр, просматриваемый пользователем в данный момент
  • self.genre = ko.observable();
  • // Доступные жанры
  • self.genres = ["Action", "Drama", "Fantasy", "Horror", "Romantic Comedy"];
  • // Добавляем JSON-массив объектов movie
  • // в модель представления
  • function addMovies(data) {
  • var mapped = ko.utils.arrayMap(data, function (item) {
  • return new movie(item);
  • self.movies(mapped);
  • // Обратный вызов для получения ошибок от сервера
  • function onError(error) {
  • self.error("Error: " + error.status + " " + error.statusText);
  • // Получаем список фильмов по жанру
  • // и обновляем модель представления
  • self.getByGenre = function (genre) {
  • self.error(""); // очистка ошибки
  • self.genre(genre);
  • // Инициализируем приложение, получая первый жанр
  • self.getByGenre(self.genres);
  • // Создаем экземпляр модели представления и передаем в Knockout
  • ko.applyBindings(new ViewModel());
  • Заметьте, что фильмы находятся в observableArray. Как и подразумевает его имя, observableArray действует как массив, уведомляющий подписчиков об изменении своего содержимого.

    Функция getByGenre выдает AJAX-запрос серверу на получение списка фильмов, а затем заполняет результатами массив self.movies.

    При использовании REST API одна из самых хитрых частей - обработка асинхронной природы HTTP. jQuery-функция ajax возвращает объект, реализующий Promises API. Вы можете задействовать метод then объекта Promise, чтобы установить обратный вызов, инициируемый, когда AJAX-вызов завершается успешно, и еще один обратный вызов, запускаемый при неудачном AJAX-вызове:

  • app.service.byGenre(genre).then(addMovies, onError);
  • Привязки данных

    Теперь, когда у меня есть модель представления, я могу связать ее с HTML через привязки данных. Для полного списка жанров, который появляется на левой стороне экрана, я использую следующие привязки данных:

  • Атрибут data-bind содержит одно или более объявлений привязок, где каждая привязка имеет форму "привязка: выражение". В этом примере привязка foreach сообщает Knockout перебирать в цикле содержимое массива genres в модели представления. Для каждого элемента в массиве Knockout создает новый элемент

  • . Привязка text в присваивает text в span значение элемента массива, каковой в данном случае является названием жанра.

    На данный момент щелчок названия жанра ни к чему не приводит, поэтому я добавляю привязку click для обработки событий щелчка:

  • Это связывает событие щелчка с функцией getByGenre в модели представления. Здесь нужно было использовать $parent, так как эта привязка осуществляется в контексте foreach. По умолчанию привязки в foreach ссылаются на текущий элемент в цикле.

    Рис. 10. Добавление привязок в таблицу для отображения списка фильмов

    На рис. 10 привязка foreach перебирает в цикле массив объектов movie. Внутри foreach привязки text ссылаются на свойства текущего объекта.

    Привязка visible в элементе

    контролирует, визуализируется ли таблица. Таблица будет скрыта, если массив movies пуст.

    Наконец, вот привязки для сообщения об ошибке и сообщения "No records found" (заметьте, что вы можете помещать в привязку сложные выражения):

    No records found.

    Редактирование записей

    Последняя часть этого приложения дает возможность пользователю редактировать записи в таблице. Для этого необходима следующая функциональность:

    • переключение между режимами просмотра (только текст) и редактирования (элементы управления вводом);
    • передача обновлений на сервер;
    • поддержка отмены изменений и восстановление исходных данных.

    Чтобы отслеживать режим просмотра/редактирования, я добавил булев флаг в объект movie как наблюдаемое свойство:

  • function movie(data) {
  • // Другие свойства опущены
  • self.editing = ko.observable(false);
  • Мне нужно было, чтобы таблица фильмов отображала текст, когда свойство editing равно false, но переключалась на элементы управления вводом, когда оно - true. Для этого я использовал Knockout-привязки if и ifnot, как показано на рис. 11 . Синтаксис "" позволяет включать привязки if и ifnot без их размещения внутри элемента HTML-контейнера.

    Рис. 11. Поддержка редактирования записей о фильмах

  • Привязка value задает значение элемента управления вводом. Это двухсторонняя привязка, поэтому, когда пользователь вводит что-то в текстовое поле или изменяет выбор в раскрывающемся списке, изменение автоматически распространяется на модель представления.

    Я связал обработчики щелчка кнопок с функциями save, cancel и edit в модели представления.

    Функция edit проста. Достаточно установить флаг editing в true:

  • self.edit = function (item) {
  • item.editing(true);
  • Функции save и cancel немного посложнее. Для поддержки отмены мне нужен был какой-то способ кеширования исходного значения при редактировании. К счастью, Knockout упрощает расширение поведения наблюдаемых объектов. В коде на рис. 12 добавляется функция store в класс observable. Вызов функции store из observable придает этому классу две новые функции: revert и commit.

    Рис. 12. Расширение ko.observable функциями revert и commit

  • var self = this;
  • var oldValue = self();
  • read: function () {
  • return self();
  • write: function (value) {
  • oldValue = self();
  • self(value);
  • this.revert = function () {
  • self(oldValue);
  • this.commit = function () {
  • oldValue = self();
  • return this;
  • Теперь я могу вызвать функцию store, чтобы добавить эту функциональность в модель:

  • function movie(data) {
  • // ...
  • // Новый код:
  • self.Title = ko.observable(data.Title).store();
  • self.Year = ko.observable(data.Year).store();
  • self.Rating = ko.observable(data.Rating).store();
  • self.Genre = ko.observable(data.Genre).store();
  • Рис. 13 демонстрирует функции save и cancel в модели представления.

    Рис. 13. Добавление функций save и cancel

  • self.cancel = function (item) {
  • revertChanges(item);
  • item.editing(false);
  • self.save = function (item) {
  • app.service.update(item).then(
  • function () {
  • commitChanges(item);
  • function (error) {
  • onError(error);
  • revertChanges(item);
  • }).always(function () {
  • item.editing(false);
  • function commitChanges(item) {
  • for (var prop in item) {
  • if (item.hasOwnProperty(prop) && item.commit) {
  • item.commit();
  • function revertChanges(item) {
  • for (var prop in item) {
  • if (item.hasOwnProperty(prop) && item.revert) {
  • item.revert();
  • Создание веб-клиента с применением Ember

    Для сравнения я написал другую версию своего приложения, используя библиотеку Ember.js.

    Ember-приложение начинает с таблицы маршрутизации (routing table), которая определяет навигацию пользователя в рамках приложения:

  • window.App = Ember.Application.create();
  • App.Router.map(function () {
  • this.route("about");
  • this.resource("genres", function () {
  • this.route("movies", { path: "/:genre_name" });
  • Первая строка кода создает Ember-приложение. Вызов Router.map создает три маршрута. Каждый маршрут соответствует URI или шаблону URI:

    /#/about /#/genres /#/genres/genre_name

    Для каждого маршрута вы создаете HTML-шаблон, используя библиотеку шаблонов Handlebars.

    В Ember имеется шаблон верхнего уровня для всего приложения. Этот шаблон подвергается рендерингу для каждого маршрута. На рис. 14 показан шаблон application для моего приложения. Как видите, этот шаблон в основном является HTML-кодом, размещаемым в теге script с type="text/x-handlebars". Шаблон содержит специальную разметку Handlebars в двойных фигурных скобках: {{ }}. Эта разметка служит той же цели, что и атрибут data-bind в Knockout. Например, {{#linkTo}} создает ссылку на маршрут.

    Рис. 14. Шаблон Handlebars уровня приложения

  • ko.observable.fn.store = function () {
  • var self = this;
  • var oldValue = self();
  • var observable = ko.computed({
  • read: function () {
  • return self();
  • write: function (value) {
  • oldValue = self();
  • self(value);
  • this.revert = function () {
  • self(oldValue);
  • this.commit = function () {
  • oldValue = self();
  • return this;
  • Movies
  • {{outlet}}
  • 2013 Mike Wasson

  • Теперь допустим, что пользователь переходит к /#/about. Это активирует маршрут about. Ember сначала осуществляет рендеринг шаблона application верхнего уровня, затем шаблона about в {{outlet}} шаблона application. Вот шаблон about:

  • Movies App
  • About this app...
  • На рис. 15 показано, как выполняется рендеринг шаблона about в шаблоне application.


    Рис. 15. Рендеринг шаблона about

    Поскольку у каждого маршрута свой URI, история браузера сохраняется. Пользователь может осуществлять навигацию кнопкой Back, а также обновлять страницу, не теряя контекст или закладку, и перезагружать ту же страницу.

    Контроллеры и модели в Ember

    В Ember каждый маршрут имеет модель и контроллер. Модель содержит данные предметной области. Контроллер действует как прокси для модели и хранит все данные состояния приложения для представления. (Это не совпадает с классическим определением MVC. В некоторых отношениях контроллер больше похож на модель представления.)

    Вот как я определил модель movie:

  • App.Movie = DS.Model.extend({
  • Title: DS.attr(),
  • Genre: DS.attr(),
  • Year: DS.attr(),
  • Rating: DS.attr(),
  • Контроллер наследует от Ember.ObjectController (рис. 16 ).

    Рис. 16. Контроллер Movie наследует от Ember.ObjectController

  • App.MovieController = Ember.ObjectController.extend({
  • isEditing: false,
  • actions: {
  • edit: function () {
  • this.set("isEditing", true);
  • save: function () {
  • this.content.save();
  • this.set("isEditing", false);
  • cancel: function () {
  • this.set("isEditing", false);
  • this.content.rollback();
  • Здесь происходит кое-что интересное. Во-первых, я не указывал модель в классе контроллера. По умолчанию маршрут автоматически устанавливает модель в контроллере. Во-вторых, функции save и cancel используют средства транзакций, встроенные в класс DS.Model. Для отмены изменений просто вызовите функцию rollback модели.

    Ember использует массу соглашений по именованию для подключения различных компонентов. Маршрут genres взаимодействует с GenresController, который выполняет рендеринг шаблона genres. По сути, Ember будет автоматически создавать объект GenresController, если вы его не определили. Однако вы можете переопределять все, что предлагается по умолчанию.

    В своем приложении я сконфигурировал маршрут genres/movies на использование другого контроллера, реализовав точку подключения (hook) renderTemplate. Тем самым несколько маршрутов может использовать один и тот же контроллер (рис. 17 ).

    Рис. 17. Несколько маршрутов могут иметь общий контроллер

  • App.GenresMoviesRoute = Ember.Route.extend({
  • serialize: function (model) {
  • return { genre_name: model.get("name") };
  • renderTemplate: function () {
  • this.render({ controller: "movies" });
  • afterModel: function (genre) {
  • var controller = this.controllerFor("movies");
  • var store = controller.store;
  • return store.findQuery("movie", { genre: genre.get("name") })
  • .then(function (data) {
  • controller.set("model", data);
  • Одна из приятных особенностей Ember в том, что многое можно делать с помощью минимума кода. Мое приложение-пример состоит примерно из 110 строк кода на JavaScript. Эта версия короче, чем версия на основе Knockout, и вдобавок я безо всяких усилий получил поддержку истории браузера. С другой стороны, Ember также является весьма "своенравной" инфраструктурой. Если вы не пишете код в стиле Ember, то скорее всего попадете в неприятности. Так что при выборе инфраструктуры следует принимать во внимание набор функциональности, стиль кодирования и то, насколько общая архитектура инфраструктуры подходит под ваши требования.

    Где узнать больше

    В этой статье я показал, как JavaScript-инфраструктуры упрощают создание SPA. Попутно я рассказал о некоторых общих средствах этих библиотек, в том числе о связывании с данными, маршрутизации и шаблонах MVC и MVVM. Узнать больше о создании SPA с помощью ASP.NET можно по ссылке asp.net/single-page-application .

    Майк Уоссон (Mike Wasson) - программист и технический писатель в Microsoft. Многие годы занимался документированием мультимедийной части Win32 API. В настоящее время пишет о ASP.NET с основным акцентом на Web API. С ним можно связаться по адресу [email protected] .

    Выражаю благодарность за рецензирование статьи эксперту Microsoft Хиньяну Чу (Xinyang Qiu).

    Одностраничные приложения

    В этой и последующих статьях будет описано средство Web API , которое является относительно новым дополнением платформы ASP.NET и позволяет быстро и легко создавать веб-службы, предоставляющие API-интерфейс для HTTP-клиентов.

    Средство Web API основано на том же базисе, что и приложения ASP.NET MVC Framework, но не является частью инфраструктуры ASP.NET MVC Framework. Вместо этого в Microsoft взяли набор ключевых классов и связанных с ними характеристик из пространства имен System.Web.Mvc и продублировали его в пространстве имен System.Web.Http .

    Идея в том, что Web API - это часть главной платформы ASP.NET и может использоваться в других типах веб-приложений либо в качестве автономного механизма веб-служб. Одним из главных применений средства Web API считается создание одностраничных приложений (single-page application - SPA) путем комбинирования Web API с возможностями ASP.NET MVC Framework. Далее будет показано, что собой представляют SPA-приложения и как они работают.

    Упрощение создания веб-служб является неотъемлемой особенностью Web API. Оно представляет собой значительное улучшение по сравнению с другими технологиями построения веб-служб, которые компания Microsoft предлагала на протяжении последнего десятилетия. Мне нравится средство Web API, и вы должны использовать его в своих проектах, не в последнюю очередь потому, что оно отличается простотой и построено на базе того же самого проектного решения, что и ASP.NET MVC Framework.

    Термин одностраничное приложение (SPA) применяется довольно широко. Наиболее согласованным является его определение как веб-приложения, начальное содержимое которого доставляется в виде комбинации HTML-разметки и кода JavaScript, а последующие операции выполняются с участием веб-службы REST, доставляющей данные в формате JSON в ответ на запросы Ajax.

    Это отличается от того вида приложений, которые строились в примерах ранее, где результатами операций, выполняемых пользователем, были новые HTML-документы, генерируемые в ответ на синхронные HTTP-запросы. Такие приложения будут называться приложениями полного обмена (round-trip application - RTA) .

    Преимущества приложения SPA связаны с тем, что оно требует меньшей полосы пропускания и пользователь получает более гладкий интерфейс. Недостатки касаются того, что такой более гладкий интерфейс может оказаться трудным в достижении, а сложность кода JavaScript, требуемого для приложения SPA, означает необходимость в тщательном проектировании и тестировании.

    В большинстве приложений приемы SPA и RTA смешиваются, при этом каждая крупная область функциональности приложения доставляется как SPA, а навигация между областями функциональности управляется с применением стандартных HTTP-запросов, которые создают новый HTML-документ.

    Пример одностраничного приложения

    Для целей этих статей в Visual Studio создан новый проект MVC по имени WebServices с использованием шаблона Empty (Пустой). В разделе Add folders and core references for (Добавить папки и основные ссылки для) были отмечены флажки MVC и Web API, как показано на рисунке ниже:

    Этот проект будет использоваться для создания обычного приложения ASP.NET MVC Framework, после чего с помощью Web API будет создана веб-служба. По готовности веб-службы приложение ASP.NET MVC Framework будет превращено в одностраничное приложение.

    Создание модели

    Приложение будет создавать и поддерживать набор заявок на бронирование помещений. Приложение планируется сохранять простым, чтобы можно было сосредоточиться на механике описываемого средства, поэтому заявки на бронирование будут состоять только из имени заказчика и местоположения помещения. В папку Models добавлен файл класса по имени Reservation.cs, содержимое которого показано в примере ниже:

    Namespace WebServices.Models { public class Reservation { public int ReservationId { get; set; } public string ClientName { get; set; } public string Location { get; set; } } }

    Планируется создать простую коллекцию объектов Reservation, хранящуюся в памяти, которая будет действовать в качестве хранилища данных. Нет необходимости заниматься установкой базы данных, однако нужна возможность выполнения операций CRUD над коллекцией объектов модели, что позволит продемонстрировать ряд важных аспектов Web API. В папку Models добавляется также файл класса по имени ReservationRepository.cs:

    Using System.Collections.Generic; using System.Linq; namespace WebServices.Models { public class ReservationRepository { private static ReservationRepository repo = new ReservationRepository(); public static ReservationRepository Current { get { return repo; } } private List data = new List { new Reservation { ReservationId = 1, ClientName = "Петр", Location = "Отель"}, new Reservation { ReservationId = 2, ClientName = "Вася", Location = "Библиотека"}, new Reservation { ReservationId = 3, ClientName = "Игорь", Location = "Столовая"}, }; public IEnumerable GetAll() { return data; } public Reservation Get(int id) { return data.Where(r => r.ReservationId == id).FirstOrDefault(); } public Reservation Add(Reservation item) { item.ReservationId = data.Count + 1; data.Add(item); return item; } public void Remove(int id) { Reservation item = Get(id); if (item != null) { data.Remove(item); } } public bool Update(Reservation item) { Reservation storedItem = Get(item.ReservationId); if (storedItem != null) { storedItem.ClientName = item.ClientName; storedItem.Location = item.Location; return true; } else { return false; } } } }

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

    Класс хранилища имеет начальный список из трех объектов Reservation и определяет методы, которые позволяют просматривать, добавлять, удалять и обновлять коллекцию. Поскольку постоянство в хранилище отсутствует, любые изменения, вносимые в хранилище, теряются в результате останова и перезапуска приложения, но этот пример целиком сосредоточен на способе, которым содержимое может быть доставлено, а не на том, каким образом оно хранится на сервере. Для обеспечения определенной доли постоянства между запросами создается экземпляр класса ReservationRepository, который доступен через статическое свойство Current.

    Установка пакетов NuGet

    В этой и последующих статьях будут применяться три пакета NuGet: jQuery, Bootstrap и Knockout. Библиотеки jQuery и Bootstrap уже были описаны и использовались ранее. Knockout - это библиотека, которую в Microsoft приспособили для одностраничных приложений. Она была создана Стивом Сандерсоном. Несмотря на то что Стив работает в Microsoft, пакет Knockout доступен в виде открытого кода на веб-сайте библиотеки Knockout и получил широкое распространение. Позже будет показано, как функционирует Knockout, а пока нужно установить упомянутые выше пакеты.

    Выберите пункт меню Tools --> Library Package Manager --> Package Manager Console (Сервис --> Диспетчер библиотечных пакетов --> Консоль диспетчера пакетов), чтобы открыть окно командной строки NuGet, и введите следующие команды:

    Install-Package jquery -version 1.10.2 -projectname WebServices Install-Package bootstrap -version 3.0.0 -projectname WebServices Install-Package knockoutjs -version 3.0.0 -projectname WebServices

    Добавление контроллера

    В пример проекта добавляется контроллер по имени Home, определение которого можно видеть в примере:

    Using WebServices.Models; using System.Web.Mvc; namespace WebServices.Controllers { public class HomeController: Controller { ReservationRepository repository = ReservationRepository.Current; public ViewResult Index() { return View(repository.GetAll()); } public ActionResult Add(Reservation item) { if (ModelState.IsValid) { repository.Add(item); return RedirectToAction("Index"); } else return View("Index"); } public ActionResult Update(Reservation item) { if (ModelState.IsValid && repository.Update(item)) return RedirectToAction("Index"); else return View("Index"); } } }

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

    Добавление компоновки и представлений

    Чтобы сгенерировать содержимое для приложения, создается папка Views/Shared, в которую добавляется файл представления по имени _Layout.cshtml с содержимым, показанным в примере ниже:

    @ViewBag.Title @RenderSection("Scripts") @RenderSection("Body")

    В этой базовой компоновке предусмотрены элементы для файлов CSS библиотеки Bootstrap. В компоновке определены два раздела, Scripts и Body, которые будут использоваться для вставки содержимого внутрь компоновки. Следующий шаг заключается в создании представления верхнего уровня для приложения. Хотя далее будет создаваться обычное приложение ASP.NET MVC Framework, известно, что в конечном итоге должно быть построено одностраничное приложение.

    Трансформацию делать будет проще, если создать единственное представление, которое содержит всю HTML-разметку, требуемую для приложения, даже при условии, что результат первоначально выглядит несколько странно. В папку Views/Home добавляется файл представления по имени Index.cshtml, содержимое которого приведено в примере ниже:

    @using WebServices.Models @model IEnumerable @{ ViewBag.Title = "Заявки на бронирование"; } @section Scripts { } @section Body { @Html.Partial("Summary", Model) @Html.Partial("Editor", new Reservation()) }

    Модель представления для этого представления является перечислением объектов Reservation, и для обеспечения строительных блоков функциональности, которую будет видеть пользователь, создаются два частичных представления. Файл с первым частичным представлением называется Summary.cshtml. Этот файл создан в папке Views/Home:

    @model IEnumerable Все заказы

  • Save
  • Cancel
  • Edit
    IDИмяПомещение @foreach (var item in Model) { }
    @item.ReservationId @item.ClientName @item.Location @Html.ActionLink("Удалить", "Remove", new { id = item.ReservationId }, new { @class = "btn btn-xs btn-primary" })

    Модель представления для частичного представления - то же самое перечисление объектов Reservation, и оно используется для генерации стилизованной таблицы с помощью Bootstrap в виде элемента

    , который отображает значения свойств этих объектов. Вспомогательный метод Html.ActionLink() применяется для генерации ссылки, которая будет вызывать действие Remove контроллера Home; ссылка стилизована в виде кнопки с использованием Bootstrap.

    Еще одно частичное представление называется Editor.cshtml и также находится в папке Views/Home. Содержимое этого файла приведено в примере ниже. Частичное представление содержит форму, которая применяется для создания новых заявок на бронирование. Отправка формы приводит к вызову действия Add контроллера Home.

    @model WebServices.Models.Reservation Создать заказ @using (Html.BeginForm("Add", "Home")) { Имя клиента @Html.TextBoxFor(m => m.ClientName, new { @class = "form-control" }) Помещение @Html.TextBoxFor(m => m.Location, new { @class = "form-control" }) Сохранить }

    Установка стартового URL и тестирование приложения

    Последний подготовительный шаг связан с установкой местоположения, куда Visual Studio будет переходить при запуске приложения. Выберите пункт WebServices Properties (Свойства WebServices) в меню Project (Проект) среды Visual Studio, в открывшемся диалоговом окне перейдите на вкладку Web и отметьте переключатель Specific Page (Определенная страница) в категории Start Action (Начальное действие). Вводить какое-либо значение не нужно - достаточно только выбора переключателя.

    Чтобы протестировать приложение в его классической форме ASP.NET MVC Framework, выберите пункт Start Debugging (Запустить отладку) в меню Debug среды Visual Studio. Вы увидите (немного странную) компоновку в стиле "все в одном", которая предоставляет пользователю список текущих заявок на бронирование вместе с возможностью их создания и удаления:

    В следующей статье мы добавим средства Web API для нашего приложения.

    , , Комментарии: 0

    В обновлении Web Tools 2012.2 для проектов ASP.NET MVC 4 добавился новый шаблон - Single Page Application (SPA). Этот шаблон предназначен для быстрого построения интерактивных веб-приложений на стороне клиента.

    "Single Page Application" (SPA) – это основной термин для веб-приложений, которые загружают одну страницу и затем обновляют ее динамически без загрузки других страниц. Загружена основная страница, и дальше приложение общается с сервером с помощью AJAX-запросов.

    AJAX – это не новинка, но сегодня существуют Javascript-библиотеки, которые упрощают разработку и поддержку больших и сложных одностраничных приложений. А HTML5 и CSS3 помогают созданию красивого пользовательского интерфейса.

    Рассмотрим пример построения веб-приложения, используя одностраничный шаблон. Построим приложение, которое управляет списком запланированных дел (to-do list).

    Создаем новое одностраничное приложение

    Для создания приложения потребуется:

    • Visual Studio 2012 или Visual Studio Express 2012 for Web
    • Обновление ASP.NET Web Tools 2012.2, его можно устьановить отсюда.

    В главном меню приложения Visual Studio откроем File-> New -> Project . Из предложенных шаблонов проектов выберем ASP.NET MVC 4 Web Application .



    Запустим приложение. Откроется форма для авторизации.


    Зарегистрируемся.

    После входа в приложение создается список дел по умолчанию с двумя элементами и появляется кнопка "Add Todo List" для добавления нового списка.


    Архитектура одностраничного приложения

    На диаграмме изображены основные блоки приложения.


    На стороне сервера ASP.NET MVC генерирует HTML, а также управляет forms-аутентификацией. ASP.NET Web API обрабатывает все запросы, касающиеся списков дел и элементов этих списков. Это – получение, создание, обновление и удаление. Клиент обменивается данными c Web API в формате JSON.

    Entity Framework является слоем ORM (object relational mapping), который связывает объектно-ориентированную архитектуру приложения с базой данных. База данных используется локальная (LocalDb), но можно подключить и свою в файле web.config. Обычно для локальной разработки используется локальная база данных, а потом с помощью миграции EF code-first она переносится на SQL server.

    В последнее время в работе специалистов Netpeak Agency много сайтов, использующих AJAX-технологии , а также сайтов написанных на JavaScript фреймворках. Без дополнительной оптимизации они не готовы к продвижению в поисковых системах. Поэтому я детально опишу, что такое SPA-сайты, как они работают, и как можно сделать Single Page Application SEO-Friendly.

    Статья будет полезна SEO-специалистам и владельцам сайтов, которые хотят перейти на Single Page Application, но не решаются, потому что SPA могут «поссориться» с поисковыми системами.

    Что такое SPA

    SPA (Single Page Application ) — одностраничное JavaScript приложение, которое запускается и работает в браузере. В отличии от «традиционного» сайта, архитектура на SPA-сайтах построена так, что рендеринг страницы полностью происходит на стороне клиента, а не на стороне сервера.

    В браузере пользователя запускается JavaScript-приложение, а все необходимое содержимое страниц динамически загружается с помощью AJAX. Навигация по сайту происходит без перезагрузки страниц. За счет такой архитектуры, SPA-сайты работают быстрее, чем «традиционные» сайты.

    Рассмотрим детальнее, как происходит загрузка и рендеринг содержимого на SPA-сайтах:

  • Пользователь запрашивает HTML содержимое сайта.
  • В ответ приходит JavaScript-приложение.
  • Приложение определяет, на какой странице находится пользователь, и какое содержимое ему нужно отобразить.
  • С помощью AJAX пользователь получает необходимый контент: CSS, JS, HTML и текстовый контент.
  • JavaScript-приложение обрабатывает полученные данные и отображает их в браузере.
  • При навигации по сайту обновляется не вся страница, а только необходимое содержимое.
  • Плюсы SPA-сайтов:

    • высокая скорость работы;
    • быстрая разработка;
    • создание версий для разных платформ на основе готового кода (desktop и mobile приложения) .

    Минусы SPA-сайтов:

    • JavaScript не обрабатывается большинством поисковых систем;
    • SPA-сайты не работают без включенного JS в браузере;
    • их нельзя анализировать на предмет ошибок популярными программами и инструментами (например, Netpeak Spider).

    Популярные JavaScript фреймворки:

    • Angular ;
    • Meteor ;
    • React ;
    • Backbone ;
    • Ember ;
    • Vue ;
    • Polymer ;
    • Knockout .
    Какие поисковые системы индексируют SPA-сайты

    На сегодняшний день только поисковый робот Google умеет рендерить содержимое SPA-сайтов, так как использует для рендеринга инструменты на базе Chrome 41. ASK.com использует выдачу Google. Для остальных поисковых систем необходимо наличие контента в коде в формате HTML.


    Оптимизация индексации SPA-сайтов

    Роботы Google и Yandex могут проиндексировать Single Page Application, если структура сайта соответствует определенным правилам. При этом, для Яндекса необходимо обязательное наличие полной HTML-копии страницы.

    Для Google нужно использовать только правильный формат URL. Летом 2018 Google перестанет индексировать HTML-копии страниц , а будет использовать только рендеринг.

    (!) Нельзя запрещать индексирование JS и CSS файлов для поисковых роботов Google. Ограничив их индексацию, Google не сможет проиндексировать содержимое SPA-сайтов.

    Существует два способа «заставить» поисковых роботов индексировать AJAX-страницы:

  • Использовать «?_escaped_fragment_=» .
  • Отдавать роботу HTML-копии определяя его по user-agent .
  • Использование «?_escaped_fragment_=»

    Этот способ подразумевает генерацию HTML-копий страниц (снимков) по отдельному URL с использованием параметра «?_escaped_fragment_» .

    Использование URL c «#!»

    Если адреса AJAX страниц формируются с помощью «#» (хеш) , значит нужно заменить их на «#!» (хешбенг) . Например, с https://example.com/#url на https://example.com/#!url .

    Google просканирует содержимое по основному URL, а робот Яндекса, обнаружив в URL «#!», запросит снимок страницы. Он заменит «#!» на «?_escaped_fragment_=» и просканирует ее по адресу https://example.com/?_escaped_fragment_=url .

    Примеры адресов c «#!» и HTML-копий страниц:

    • https://example.com/home#!page → https://example.com/home?_escaped_fragment_=page ;
    • https://example.com/index/#!main → https://example.com/index/?_escaped_fragment_=main ;
    • https://example.com/#!home/index → https://example.com/?_escaped_fragment_=home/index .
    Использование «традиционных» URL

    Если на сайте используются «традиционные» URL (https://example.com/url), необходимо указать на всех страницах мета-тег:

    Google просканирует содержимое по основному URL, а робот Яндекса, обнаружив на странице мета-тег , запросит HTML-копию страницы. Также добавит в URL параметр «?_escaped_fragment_=» и просканирует ее по адресу https://example.com/url?_escaped_fragment_=

    Для популярных фреймворков есть готовые решения, которые заменяют «#!» на «традиционные» URL, например, HTML5 mode для Angular .

    (!) В HTML-копии документа мета-тег размещать не следует: в этом случае страница не будет проиндексирована.

    (!) На страницах HTML-копий canonical должен либо отсутствовать, либо вести сам на себя.

    Например, на странице https://example.com/home?_escaped_fragment_= должен быть указан следующий canonical:

    Отдавать HTML-копии страницы по основному URL

    Этот способ подходит для SPA-сайта с «традиционными» URL. В чем суть: поисковый робот, запрашивая страницу по основному URL, вместо динамической версии получает HTML-копию страницы.

    Определить поискового робота можно по User-Agent. К примеру, по списку роботов Яндекса .

    Рендеринг HTML-копий

    Для того, чтобы поисковые системы смогли проиндексировать содержимое, необходимо реализовать создание HTML-копий страниц.

    HTML-копии — это отрендеренные версии страниц SPA-сайта. Например, содержимое исходного кода страницы SPA-сайта выглядит так:

    Single Page Application

    А вот отрендеренная HTML-копия этой страницы:

    TITLE страницы СОДЕРЖИМОЕ СТРАНИЦЫ


    Для создания HTML-копий страниц можно:

  • Использовать рендеринг на своих серверах.
  • Использовать рендеринг на аутсорсе.
  • Использовать изоморфный JavaScript.
  • Существуют много готовых решений для рендеринга HTML-копий, поэтому, если вы используете популярный фреймворк (например, Angular) , на внедрение HTML-копий страниц должно уйти всего несколько часов разработчика.

    Рендеринг на своих серверах

    Подходит для крупных проектов. Для рендеринга HTML-копий на своих серверах можно использовать следующие инструменты:

    • PhantomJS ;
    • Angular Universal ;
    • Angular-SEO .
    Рендеринг на аутсорсе

    Подходит для небольших проектов. Для рендеринга HTML-копий на сторонних серверах можно использовать такие инструменты:

    • BromBone ;
    • Prerender.io ;
    • RenderJS.io .
    Изоморфный JavaScript

    Изоморфный JavaScript позволяет выполнять рендеринг страницы как на сервере, так и в браузере пользователя. Таким образом, при первой загрузке страницы пользователь получает обычную HTML-страницу и JavaScript-приложение. Дальнейшая навигация по сайту для пользователя будет динамической, в то время как поисковые системы каждый раз будут получать HTML-версию страницы.

    Такой подход к разработке сайта позволяет убить сразу двух зайцев: сайт получает все преимущества SPA-сайта и возможность продвижения в поисковых системах без дополнительных внедрений.

    Рендеринг страницы на SPA-сайтах происходит на стороне клиента, поэтому без дополнительных доработок код ответа несуществующей страницы будет 200 ОК.

    SPA-сайты должны корректно обрабатывать несуществующие страницы и отдавать в качестве заголовка страницы 404 ошибку.

    Настройка Google Analytics

    Стандартный код Universal Analytics выполняется при загрузке каждой новой страницы, а SPA-сайты подгружают контент динамически, «имитируя» переход между страницами. Для того, чтобы Google Analytics корректно обрабатывал переходы на страницах, необходимо сделать так, чтобы счетчик Universal Analytics активировался каждый раз, когда на сайте меняется URL.

    Настроить Google Analytics для SPA-сайтов можно, используя Tag Manager и триггер «History» или — передавая данные вручную.

    С помощью Tag Manager и триггера «History» Выводы

    SPA-сайты — это настоящее и будущее, поэтому не нужно бояться брать в работу такие проекты. Single Page Application можно «подружить» с поисковыми системами. Чтобы ваш SPA-сайт был дружелюбен как к SEO, так и к пользователям я рекомендую:

  • Использовать «традиционные» URL.
  • Использовать изоморфные приложения или рендеринг HTML-копий.
  • Настроить правильную отдачу заголовка «404 Not Found».
  • Настроить корректную работу Google Analytics.
    • Tutorial

    Одностраничные приложения (SPA) имеют мнжество преимуществ, таких как скорость, по-настоящему хороший UX, и полный контроль HTML-разметки. Становится всё больше и больше сайтов SPA; всё больше инструментов, которые упрощают процесс разработки SPA. Вы, вероятно уже читали о молодом и перспективном фреймворке Vue.js . Предлагаю вам глубже погрузиться в Vue и на конкретном примере разобраться с простым SPA.

    Мы напишем клиент-серверное приложение простейшего блога. Приложение будет отображать список записей а также полный текст каждой отдельной записи. И само собой, всё это будет происходить без перезагрузки страницы.

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

    Бэкенд В этом руководстве мы в основном сосредоточимся на фронтенде на Vue . Размышлять о написании REST бэкенда мы не будем. Для примера будет использоваться сервис jsonplaceholder.typicode.com предостовляющий заглушку в виде REST API.ФронтендИнструменты Начать работу с Vue очень просто. С использованием правильных инструментов это ещё проще. Рекомендую взглянуть на проект vue-awesome , который содержит список инструментов, компонентов, библиотек и плагинов на все случаи жизни.Vue-cli При создании нового проекта рекомендуется воспользоваться Vue-cli. Так можно создавать проекты с использованием официальных шаблонных проектов Vue, или одного из множества шаблонных проектов с открытым исходным кодом, и, конечно же, вы можете создать свой собственный и использовать его в любом месте.

    Итак, для начала установим vue-cli в качестве глобального пакета:

    $ npm install -g vue-cli
    Затем проинициализируем проект с выбранным шаблоном; для нашего примера более чем достаточно использовать webpack-simple.

    $ vue init webpack-simple vue-spa
    Далее перейдём в папку vue-spa и запустим npm install в терминале. После установки всех пакетов, мы можем запустить наше приложение в режиме разработки.

    $ npm run dev
    Эта команда автоматически запустит наш проект на локальном dev-сервере webpack. В браузере появится наше простейшее приложение Vue. Конечно оно выглядит совсем не так, как бы нам хотелось, и годится лишь в качестве отправной точки для начала чего-то большего. Чтобы продолжить работу, предлагаю сначала ознакомиться со структурой нашего шаблона.

    Внутри шаблон webpack-simple имеет следующую структуру:

    Файл index.html содержит простую разметку HTML с единственным элементом “app” в body. Он будет заменён на DOM, сгенерированный vue. По этой причине тэг body не рекомендуется использовать в качестве корневого элемента.

    В папке src лежит файл main.js, который содержит точку входа webpack. Компоненты Vue импортируются там же. Ещё там описан корневой экземпляр Vue, который пока что имеет два свойства. Свойство ‘el’ обеспечивает экземпляру Vue связь с указанным DOM элементом. Ещё одно - функция отрисовки, генерирующая DOM из App.vue . В общем, это все, что нам нужно знать о структуре шаблона webpack-simple, не так много, не так ли? Основная часть нашего приложения будет запрограммирована в App.vue. Расширение.vue определяет файл как однофайловый компонент vue. Это одна из особенностей Vue с которой мы сейчас познакомимся поближе.

    Каждый файл *.vue состоит из блоков трёх типов: , и опционально . В результате, мы можем разделить проект на связанные компоненты. Внутри компонента его шаблон, логика и стили неотъемлемо связаны, и их совмещение фактически делает компонент более целостным и легко поддерживаемым. Теперь мы готовы приступить к созданию блога на Vue.

    Давайте посмотрим, что собственно мы собираемся реализовать. У нас будет заголовок с названием нашего блога в верхней части страницы. С левой стороны у нас будет фиксированная боковая панель, в которой мы будем отображать заголовки наших записей, это будет что-то вроде оглавления. Остальная часть страницы будет занята динамическим блоком, в котором будет отображаться сам текст записи.

    Шаг 1 Прежде всего, удалим все лишние строки из App.vue. И перепишем шаблон в соответствии с нашими требованиями.

    Vue.js SPA
    Во-вторых, создадим экземпляр Vue со свойством data, которое мы разместим в массиве с нашими сообщениями. На данный момент он пуст, но вскоре мы поместим в него данные, полученные с сервера внутрь массива.

    После первого обращения, вы больше не сможете добавлять реактивные свойства к корневому объекту данных. Поэтому, прежде чем создавать экземпляр Vue, рекомендуется объявить все реактивные свойства на уровне корня.

    export default { data () { return { posts: } } }
    Кроме того, можно добавить немного стилей, чтобы приложение выглядело лучше.
    Код приложения хостится на github.com . Достаточно клонировать репозиторий и переключать ветку по номеру шага чтобы проследить разработку приложения шаг за шагом например:

    $ git checkout step-1
    В настоящий момент нам абсолютно нечего отображать в нашей навигационной панели, поэтому давайте получим данные с сервера. Для этого я выбрал Axios - простой в использовании HTTP-клиент. Вы также можете использовать любой удобный для вас способ, например, Vue-ресурс или собственную выборку или даже jQuery Ajax.

    Шаг 2 Установим Axios

    $ npm install --save-dev axios
    Затем импортируем его в компонент App и определим метод getAllPosts() который будет делать запрос к серверу и присваивать его свойству posts. Вызываем метод в хуке created(), который будет вызываться после создания экземпляра Vue и после установки настроек обращения к данным.

    Import axios from "axios" export default { data () { return { posts: null, endpoint: "https://jsonplaceholder.typicode.com/posts/", } }, created() { this.getAllPosts(); }, methods: { getAllPosts() { axios.get(this.endpoint) .then(response => { this.posts = response.data; }) .catch(error => { console.log("-----error-------"); console.log(error); }) } } }
    А теперь отобразим все заголовки записей в боковой панели.

    {{ post.title }}
    До сих пор мы отображали только заголовки записей, но пока мы ещё не можем видеть сами записи. Теперь необходимо отобразить полный пост в разделе контента в соответствии с выбранным названием на боковой панели. В то же время хотелось бы, чтобы каждая запись была доступна по своему уникальному адресу.

    Шаг 3 Для этого воспользуемся официальной Vue библиотекой vue-router. Как уже понятно из названия, библиотека позволяет настраивать роутинг для нашего приложения.
    Установим библиотеку:

    $ npm install --save-dev vue-router
    Для настройки роутинга вернёмся к файлу main.js. Здесь мы определим настройки роутинга и добавим их в наш экземпляр Vue.

    Import Vue from "vue" import Router from "vue-router" import App from "./App.vue" import Post from "./components/Post.vue" import Hello from "./components/Hello.vue" Vue.use(Router) const router = new Router({ routes: [ { path: "/", name:"home", component: Hello, }, { path: "/post/:id", name:"post", component: Post, props: true, }, ] }) new Vue({ el: "#app", render: h => h(App), router })
    В настройках роутинга мы указали, какой компонент вызывает отрисовку по соответствующему пути. Поскольку только компонент Post.vue будет отвечать за отрисовку каждого поста, нам не потребуется определять путь к каждому посту, достаточно определить динамический путь.

    Path: "/post/:id"
    Этот путь содержит динамический сегмент:id который указывает на конкретный пост. При этом у нас есть доступ к этому сегменту в компоненте Post через this.$route.params.id . Однако использование $route в нашем компоненте закрепит жесткую связь с роутом, что в свою очередь ограничивает гибкость компонента, поскольку он может использоваться только на определенных URL-адресах. Вместо этого мы можем использовать опцию props и установить её в true . После этого $route.params станет связан с опцией props компонента Post.
    Теперь, когда мы создали роутер, мы можем вернуться к нашему приложению и добавить еще несколько строк в шаблон.

    {{post.id}}. {{post.title}}
    Здесь мы имеем два компонента vue-router : и . Первый - это компонент для включения навигации пользователя в приложении с поддержкой роутинга. Второй компонент - это функциональный компонент, который отрисовывает согласованный компонент для данного пути.

    Остался заключительный шаг. Нам нужно отобразить содержимое записи поста.

    Шаг 4 Перейдём к файлу Post.vue, в котором добавим простой шаблон:
    {{ post.title }}

    {{ post.body }}

    {{ post.id }}


    Затем нам нужно установить параметры экземпляра Vue для этого компонента. Здесь все также как в настройках отображения всех постов. Объявим опцию props с меняющимся id , которая будет получать номер нашего поста. Далее, объявим объект данных, как уже делали в App.vue:

    Import axios from "axios"; export default { props: ["id"], data() { return { post: null, endpoint: "https://jsonplaceholder.typicode.com/posts/", } } }
    Затем опишем метод getPost() , который будет получать только одну запись поста по идентификатору и вызовем его в хуке created() .

    Methods: { getPost(id) { axios(this.endpoint + id) .then(response => { this.post = response.data }) .catch(error => { console.log(error) }) } }, created() { this.getPost(this.id); },
    Почти готово. Если мы запустим приложение сейчас, мы увидим, что, хотя URL-адрес меняется, мы видим единственный пост, который был отрисован первым. Дело в том, что для отрисовки разных постов у нас есть один и тот же компонент, и Vue не нужно его пересоздавать из-за лишней траты ресурсов, а это также означает, что хуки жизненного цикла компонента не будут вызваны.
    Чтобы это исправить, нам просто нужно установить watcher для объекта $route .

    Watch: { "$route"() { this.getPost(this.id); } }
    Теперь всё работает так, как и должно. Чтобы получить версию для продакшина достаточно выполнить команду npm run build в консоли.

    Подведём итоги Мы написали простое одностраничное приложение с использованием Vue за четыре шага. Мы узнали, как легко начать свой проект с vue-cli. Мы рассмотрели концепцию однофайловых компонентов Vue, которые делают ваш проект более гибким и масштабируемым. Мы узнали, как извлекать данные из внешнего API с помощью Axios. И мы увидели, как настроить роутинг с помощью vue-router. Разумеется, это базовые знания, но я надеюсь, что это поможет вам начать использовать Vue.js используя его расширенные возможности.

     

     

    Это интересно: