Динамическое обновление веб-страницы

image

Введение


Никого уже не удивишь концепцией динамического HTML, почти все сайты давно в той или иной мере используют javascript для того, чтобы сделать страницы интерактивными. А с появлением технологии AJAX стало возможным асинхронно генерировать запросы к серверу, чтобы изменять старые данные на сервере или получать новые. Но как именно обновлять структуру страницы? Кто должен генерировать новый html — сервер или javascript? А может, все вместе?

Посмотрим, как можно ответить на эти вопросы.

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

Любое веб-приложение можно логически поделить на две составляющие — на клиентскую часть и серверную часть. К клиентской части относятся сам браузер и скрипты, которые он выполняет, к серверной — набор скриптов, которые генерируют ответ на любой запрос пользователя.

Жизнь любой страницы начинается с запроса от клиента к серверу. Ответом будет код страницы, содержащий, помимо структуры и стилей, логику клиентской части.

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

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

Ближе к сути


Для удобства объяснения рассмотрим вариант обновления простой страницы с лентой новостей и, скажем, счетчиком подписчиков. Мы хотим, чтобы браузер регулярно проверял обновления ленты, добавляя новости по мере их появления. А еще мы хотим, чтобы каждый посетитель видел динамику роста популярности нашего сайта — пусть счетчик подписчиков тоже регулярно обновляется.

Тело нашей страницы может выглядеть, например, так:

<span id="subscr_cnt">Подписчиков: 42</span>
<ul id="news">
  <li><a href="/australia">Крупнейшие на Земле метеоритные кратеры случайно нашли в Австралии</a></li>
  <li><a href="/wonderwoman">Чудо-женщина ответила на критику о своей груди</a></li>
  <li><a href="/romeo_madness">«Ромео и Джульетту» экранизируют в духе «300 спартанцев»</a></li>
</ul>

Вариант 1 — дублирование


Основная идея — логику отображения знает и клиентская, и серверная часть. В таком случае, ответы на регулярные запросы со стороны клиента могут содержать исключительно данные — изменения в модели, и выглядеть, например, так:

{
  subscr_cnt: 44,
  news:[
    {
      href: "/wiskey",
      name: "Названы лучшие в мире сорта виски 2015 года"
    },
    {
      href: "/kindergarden",
      name: "В Нью-Йорке появился детский сад для взрослых"
    }
  ]
}

При получении такого ответа клиентская часть «оборачивает» данные в html-теги, добавляет необходимые тексты и обновляет структуру страницы.

Серверу же знания об отображении нужны только для того, чтобы сгенерировать изначальную версию страницы.

Плюсы подхода:
  • Малый объем трафика — передаются только необходимые данные;


Минусы подхода:
  • Требуется продублировать код — он будет и в клиентской части, и в серверной;
  • Клиентская часть должна знать, как именно поступать с каждой порцией данных от сервера — иногда нужно заменить html элемента, иногда добавить новые данные к уже существующему коду;

Вариант 2 — всемогущий сервер и «толстые» ответы


Основная идея — логику отображения знает только сервер, клиентская часть получает уже готовый html-код элементов. Здесь ответ сервера выглядит так:

{
  subscr_cnt: "Подписчиков: 44",
  news: "<li><a href=\"/australia\">Крупнейшие на Земле метеоритные кратеры случайно нашли в Австралии</a></li> \n <li><a href=\"/wonderwoman\">Чудо-женщина ответила на критику о своей груди</a></li> <li><a href=\"/romeo_madness\">«Ромео и Джульетту» экранизируют в духе «300 спартанцев»</a></li>  <li><a href=\"/wiskey\">Названы лучшие в мире сорта виски 2015 года</a></li>  <li><a href=\"/kindergarden\">В Нью-Йорке появился детский сад для взрослых</a></li>"
}

Замечу, что пересылается здесь весь html каждого компонента на странице. Реализуется же такой способ просто — сервер генерирует страницу по кускам, клиент при получении ответа заменяет тела отдельных элементов.

Плюсы подхода:
  • Простота реализации;
  • Отсутствие дублирования кода;


Минусы подхода:
  • Многократная генерация одного и того же кода, особенно неэффективно при небольших изменениях;
  • Огромный объем трафика, особенно на больших страницах;

Вариант 2а — всемогущий сервер и «тонкие» ответы


Можно попытаться исправить главный недостаток предыдущего варианта. Сервер может не отправлять весь html компонента, а присылать только «дельту» — изменения, которые необходимо внести. Наш ответ тогда может стать таким:

{
  subscr_cnt: {
    html: "Подписчиков: 44",
    mode: "replace"
  },
  news: {
    html: "<li><a href=\"/wiskey\">Названы лучшие в мире сорта виски 2015 года</a></li>  <li><a href=\"/kindergarden\">В Нью-Йорке появился детский сад для взрослых</a></li>",
    mode: "append"
  }
}

Теперь клиент определяет элемент, который будет изменять, и то, как он его будет изменять, непосредственно из ответа сервера.

Плюсы подхода:
  • Отсутствие дублирования кода;


Минусы подхода:
  • Все еще достаточно большой объем сетевого трафика;
  • Клиент должен отправить серверу текущее состояние каждой компоненты, закодированное некоторым образом, чтобы сервер понял, относительно чего считать дельту;
  • Сложность вычисления и записи дельты в случае нетривиальных изменений;
  • Общее усложнение и клиентской, и серверной части;

Вариант 3 — всемогущий javascript


Можно переложить всю ответственность за генерацию html на клиента. В таком случае сервер будет только предоставлять данные, необходимые для отображения. Ответы, как и в первом варианте, будут содержать только данные:

{
  subscr_cnt: 44,
  news:[
    {
      href: "/wiskey",
      name: "Названы лучшие в мире сорта виски 2015 года"
    },
    {
      href: "/kindergarden",
      name: "В Нью-Йорке появился детский сад для взрослых"
    }
  ]
}

Так в чем же существенное отличие от первого варианта? А заключается оно в том, что сервер не выполняет первоначальную генерацию страницы, её сборка осуществляется уже браузером клиента. Вариант этот только выглядит странным, он может пригодиться, если необходимо уменьшить нагрузку на сервер.

Плюсы подхода:
  • Малый объем трафика — передаются только необходимые данные;
  • Уменьшение нагрузки на сервер;


Минусы подхода:
  • Высокая нагрузка на компьютер пользователя;
  • Возможна избыточность — часть знаний клиентской части об отображении может так и остаться невостребованной, если какие-то события не наступят;


Заключение


Каждый из рассмотренных методов имеет право на жизнь, и может быть использован в проектах разной сложности. Лично я во встреченных мною проектах чаще всего видел первый вариант, несмотря на нарушение им моего любимого принципа DRY — Don`t repeat yourself.

А какие принципы вы используете при разработке динамических страниц?
Поделиться публикацией
Комментарии 19
    0
    А в чем дублирования кода в первом варианте? Где он продублируется?
      +1
      В первом варианте страница первоначально генерируется сервером, при каждом запросе на обновление от клиента в ответ приходят данные, с помощью которых javascript должен сгенерировать некоторый html и встроить его в страницу. Получается, генерация html будет происходить и на стороне сервера(построение изначальной страницы), и на стороне клиента(response клиентской части на обновление от сервера).
      +9
      Вариант 4
      Изоморфные фреймворки

      Очень много таковых появилось для NodeJS. При первом запросе компоненты генерируются на сервере, в браузере происходит лишь инициализация компонент, далее компоненты могут отсылать/запрашивать данные, и обновлять себя, и рендерить другие компоненты.
        0
        интересно. не подскажете какую-нибудь статью по теме, чтобы ознакомиться?
          0
          Может на статью хорошую кто и укажет, дам пару ссылок на фреймворки: Derby, Meteor, React тоже можно запускать и рендерить на ноде. Ну и мы тоже немного разрабатываем такой подход: MaskJS, в целом это фронтэнд фреймворк, но также есть модуль который те же компоненты рендерит на ноде, а далее в браузере восстанавливает компоненты, как если бы они рендерелись на клиенте. Может немного запутано, но можно посмотреть несколько примеров в репозитории: Mask.Node
            –2
            добавлю ещё bnsf на БЭМ стеке:
            github.com/apsavin/bnsf
          0
          ИМХО, то, что ваш сервер в одном ответе возвращает разнородные данные (подписчики и новости) уже неправильно.

          Да и пункты у вас по сути не отличаются. Ответ с сервера и обработка Javascript.
          Разница только в том, кто оборачивает ответ в HTML, сервер или клиент — вставляет в любом случае JS.
            0
            Разнородные данные — только для примера, надеюсь, я никого не побудил так весело реализовывать передачу данных в настоящем проекте :)

            А по второму пункту — мне как раз было интересно, как и чем лучше оборачивать в html, в этом и есть смысл публикации.
              0
              Лучше вообще не оборачивать, по крайней мере руками. Почитайте про data binding. По сути все тот же «всемогущий javascript», но сильно упрощает и ускоряет разработку.
                –3
                Простите, но я снова БЭМ вставлю. Очень хороши их декларативные шаблонизаторы bh и связка bemtree+bemhtml. Рендерятся также универсально в браузере или nodejs.
                0
                Это называется sideloading.
                0
                В своих проектах я использовал вперемешку сразу все три варианта с небольшими отличиями) Смесь вариантов 1 + 2а удобна тогда, когда надо сначала сгенерировать HTML, который увидят в том числе и поисковые роботы, а после этого динамично обновлять это содержимое через получение событий-команд с сервера примерно в таком же формате, как в варианте 2а. Получили команду — обновили изначально сгенерированный DOM. Вариант 2 удобен для быстрого и динамичного перехода между страницами сайта, т.е. для аякс-навигации. Я тоже как-то пытался использовать его именно в таком виде, в каком он описан в статье: через передачу данных в формате JSON, но на практике это оказалось слишком затратно: браузеру приходилось сначала спарсить огромную JSON-строку и потом еще рендерить на основе этих данных новую страницу. Короче, браузер даже на глаз тормозил на компьютерах средней мощности, и чем больше страница, тем больше тормозов. Итого, мы тогда пришли к более простому варианту: сервер стал отправлять чистый HTML, который потом просто заменял содержимое центрального контейнера с основным контентом страницы, и плюс в конец страницы добавлялось несколько JS-команд, через которые заменялся заголовок страницы и какая-то другая мета-информация.
                  –4
                  Поздравляю, вы почти изобрели React!
                  facebook.github.io/react/docs/reconciliation.html
                    +1
                    В последнем варианте к минусам можно дописать возможные проблемы индексирования поисковыми машинами таких страниц, не все роботы еще умеют выполнять клиентский JS.
                      0
                      В копилку изоморфных фреймворков: CatBerry.
                      Из фишек, прогрессивный рендеринг — клиенту начинает отправляться html как можно быстрее. Таким образом браузер может уже работать с head, когда последний блок на странице еще не сгенерирован сервером. (Минус этой фичи: код ответа сервера всегда строго 200)
                        +4
                        Веб-приложение всегда должно использовать только метод 3. Более того, должно быть хорошо задокументированное API.
                        Веб-сайт может использовать все, что угодно, но лучше конечно прийти к методу 3, но без «дублирования», т.е. структура страницы и основной контент создаются сервером (для поисковиков и пр. урезанных клиентов), а динамический контент подгружается из API.
                          0
                          Ну, дублирование — это не DRY. Толстые ответы с рендерингом всей страницы — а зачем тогда Ajax? Вы через turbolinks получите примерно похожее.
                          Даже не знаю, второй подход — это как в рельсах писать все запросы на SQL, когда есть ActiveRecord. Неоправданно, в общем.
                          Третий — то, для чего эти запросы и нужны, собственно, обмен JSONами с сервером, быстро и эффективно, и обновление информации real-time, с различными красивыми fadeIn и прочим.

                          Первый случай еще имеет место быть, но только в одном случае — когда это реально экономит вам время и деньги. Второй — просто не стоит биндить рендер страницы на один эвент.

                          P.s — простите за русско-английский, с телефона не торт писать.
                            +3
                            Минусы подхода:
                            > Высокая нагрузка на компьютер пользователя;
                            С современными браузерами нагрузка не такая высокая.

                            > Возможна избыточность — часть знаний клиентской части об отображении может так и остаться невостребованной, если какие-то события не наступят;

                            Не надо просто грузить все скрипты сразу. Загружать нужно только то, что необходимо для отрисовки.

                            Самый настоящий минус в 3-ем подходе — это необходимость доработки сайта для индексации в поисковиках.
                              0
                              не стоит остекать клиентов с не очень новыми браузерами

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое