Рендер страницы в JavaScript

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

Так что начнём наше исследование на примере написания с нуля браузерного приложения. И сегодня с классического серверного рендеринга HTML-страниц перейдём на построение DOM-дерева через JavaScript. Заодно рассмотрим пользу отделения данных от представления. Выделим побочные эффекты и сделаем удобные и тестируемые чистые функции:

  • 00:00:36 Вопрос работы с фронтендом
  • 00:01:57 Статическая страница
  • 00:04:16 Корневой элемент
  • 00:04:53 Создание логотипа в JavaScript
  • 00:07:31 Генерация шапки сайта
  • 00:08:10 Вывод текущего времени
  • 00:09:26 Группировка состояния страницы
  • 00:10:56 Вывод лотов
  • 00:14:03 Проблема неструктурированного кода
  • 00:14:36 Декомпозиция на компоненты
  • 00:16:13 Всплытие определений в JavaScript
  • 00:17:12 Концепция чистой функции
  • 00:21:15 Группировка аргументов функции
  • 00:23:37 Раздельный вывод лотов
  • 00:25:30 Компонент приложения
  • 00:25:58 Процедура рендерига

А в следующем эпизоде в нашей свёрстанной странице добавим динамику. Чтобы данные подгружались по API и в реальном времени обновлялись цены через WebSocket.

Скрытый контент (код, слайды, ...) для подписчиков. Открыть →
Дмитрий Елисеев
elisdn.ru
Комментарии (26)
А
2020-08-13 19:05

Простой урок, но при этом полезная тема про распаковку структур. Очень ждал что-нибудь про React или vue

Ответить
Руслан
2020-08-14 08:13

Спасибо!

Ответить
Yevhenii Lykholai
2020-08-16 10:35

Спасибо.

Ответить
Григорий
2020-08-21 08:49

Отличный материал, давно задавался вопросом от чего отталкиваться при работе с фронтендом, и очень помогла проведённая аналогия бекендом. Спасибо!

Ответить
Кирилл
2020-08-22 08:25

За временнЫе метки - респект!

Ответить
Paul
2020-08-29 08:29

Спасибо, очень интересно!

Возник вопрос, почему в современном js так любят использовать const вместо переменных? Разве это не противоречит самой сути констант?

Ответить
Дмитрий Елисеев
2020-09-03 08:33

Вместо var теперь рекомендуется использовать let для переопределяемых значений и const для привязанных навсегда.

Константу const a=5 или const b={amount:3} нельзя позже переопределить на новое значение a=6 или b={amount:7}. В константе будет постоянно храниться указатель на первоначальный объект. Но на сам объект при этом никакое ограничение не накладывается, поэтому можно поменять любое его поле напрямую через b.amount=7.

Это противоречит сути констант из классических компилируемых языков, в которых константе можно присвоить только скалярное значение и потом компилятор везде в код подставляет напрямую это значение вместо константы.

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

Ответить
Paul
2020-09-03 21:40

Дмитрий, спасибо за ответ!

Ответить
Александр Кулик
2020-09-03 06:55

Супер. Очень доходчиво

Ответить
Aёct'ann
2020-09-11 06:10

Материал на высшем уровне!

Ответить
Tema Ovchinnikov
2020-10-04 07:51

Спасибо, очень интересно! Если использовать history api то получается если взять к примеру вк. Левая часть (меню) статическая, а правая динамическая. Это получается на сервере возвращается json и на основе этого рендерится новостная лента список друзей мета информация страницы и тд?

Таким образом при воспроизведении музыки спокойно можно кликать на разные вкладки меню без перезагрузки страницы? Если вместо json фрагмент html отправить site.local/api/news это будет не быстрее чем на клиенте рендерить?

Ответить
Дмитрий Елисеев
2020-10-15 06:35

Левая часть (меню) статическая, а правая динамическая.

Да, все фронтенд-приложения Single Page Application (SPA) используют History API вместо реальной смены адреса с переоткрытием страницы. То есть навешиваются на onCLick всех ссылок и просто меняют адрес. И в зависимости от текущего пути уже выводят разные компоненты внутри шаблона. Например, у нас мы можем внутри 'App' под вызовом Header() вызывать некую функцию вроде:

function Content ({ path }) {
  if (path === '/') {
      return Home()
  } else if (path === '/lots') {
      return Lots()
  }
}

в которую передавать window.location.pathname. Она будет работать как маршрутизатор и в итоге на странице будет заменяться только контент.

И каждый компонент-страница вроде Lots уже будет запрашивать нужный ему JSON по API.

Ответить
Дмитрий Елисеев
2020-10-15 06:52

Если вместо json фрагмент html отправить /api/news это будет не быстрее чем на клиенте рендерить?

Браузеру почти нет разницы, откуда он всё берёт. Хоть с HTML, хоть из JS. Всё равно он сначала парсит тэги из HTML в дерево элементов и только после этого их рендерит.

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

Ответить
Сергей
2020-11-20 23:33

Спасибо. Хорошо излагаете.

Ответить
Тимур
2020-12-03 20:09

Отличный материал. Но несколько "НО":

  • нужно было сказать, что есть 2 метода добавления: append и appendChild. В первом можно добавить как Node узел, так и текст. В случае с appendChild можно добавить только Node. Так как мы с помощью append добавляем только Node, лучше использовать appendChild. Есть подозрения, что в этом случае интерпритатор не проверяет тип, что экономит время
  • getElementByID устарел (хоть и работает). Для таких целей лучше использовать document.querySelector(), так будет понятнее для людей, кто пришел в профессию из верстки
  • JavaScript инициализирует все функции перед выполнением кода только тогда, когда используется декларативное описание. При использовании Function Expression такой подход вызовет ошибку, ибо функция создается только в момент объявления

А так все по делу

Ответить
Алексей
2021-01-06 22:34

Как всегда спасибо, и есть вопрос, я посмотрел пока только 6 уроков, но вижу что react даже с JSX сложен для восприятия и главное для редактирования простым верстальщиком который знает html+css, посмотрел на vue и он вроде проще для визуального восприятия и изменения верстки, вопрос почему вы выбрали React и чем он лучше Vue, спасибо!

Ответить
Дмитрий Елисеев
2021-01-07 07:39

Они идут от разных сторон. React из JS, а Vue из HTML.

React использует нативный JS с React.createElement. И просто через примитивный JSX позволяет заменять вызов React.createElement на HTML-подобный синтаксис.

А Vue изначально имеет полноценный отдельный HTML-шаблонизатор с <template>, куда уже нужно вписывать нужные ему управляющие конструкции вроде v-if и v-for.

простым верстальщиком который знает html+css

Верно. React с JSX удобнее и привычнее JS-программисту, но меньше понятен HTML-верстальщику.

А Vue своим HTML-шаблонизатором <template> удобен верстальщику, но неудобен программисту.

Ответить
Алексей
2021-01-07 10:14

Спасибо за ответ, все как всегда подробно и понятно!

Ответить
Михаил
2021-01-10 15:43

Отлично материал подается! Сам я бэкендер, но чтобы не дергать фронтовиков по мелочам и понимать, что там происходит, стараюсь быть в теме клиентской стороны. Спасибо автору, все по делу и очень понятно.

Ответить
Александр
2021-01-30 10:01

Спасибо за урок! Есть немного обратной связи:

  1. Названия функций должны быть глаголом. Дано понятие "Компонент", но не сказано, что это такое и почему функции с большой буквы. Зная твой подход, я то конечно подозреваю, что далее ты к этому подведешь и станет понятно. :)

  2. Зависимостей между функциями лучше же тоже избегать, даже если они чистые?

  3. Сокращать синтакис объявления объекта мне кажется лишним и ошибочным. Не знаю почему разработчики пошли по этому пути! :)

lots.forEach((lot) => {
    node.append(Lot({ lot: lot }));
});
lots.forEach((lot) => {
    node.append(Lot({ lot }));
});

Первый вариант мне кажется более читаемым.

Ответить
Дмитрий Елисеев
2021-02-02 15:50
  1. Компонент в React можно сделать в виде функции или в виде класса. Поэтому удобно их всегда именовать по смыслу существительным Header вместо названия-глагола renderHeader для функции и Header для класса. Ещё такое особое именование компонентов-виджетов удобно, чтобы не путать их с другими обычными функциями-глаголами.

  2. Избегать стоит только прямых зависимостей от "грязных" функций, чтобы их можно было подменять в тестах на чистые заглушки. А используемые чистые функции тестированию никак не мешают.

  3. Да, некоторым более читаемо классическое многословное написание:

function (name) {
  return { name: name }
}

вместо нового короткого аналога:

(name) => ({ name })

Синтаксический сахар практически всегда придумывается для укорачивания кода. Со временем он становится привычным.

Ответить
Олег
2021-03-07 13:31

Спасибо за как всегда блестящую подачу материала, Дмитрий! Просмотрел весь цикл до конца и вернулся в начало, чтобы переосмыслить увиденное. И неожиданно для себя обнаружил несколько однотипных тавтологий и мест, которые можно было бы упростить. Я по поводу деструктуризации. Она теряет смысл, когда в объявлении функции и при её вызове используется один объект. Таких примеров здесь несколько, например

function App ({state}) {...}

и потом в вызовах

App({state})

Здесь достаточно простого синтаксиса

function App (state) {...}

а затем

App(state)

В вызовах других методов, например, в Clock внутри App() не нужна такая подстановка аргументов, как

node.append(Clock({time: time.state}))

Вместо этого можно было просто написать

node.append(Clock(state))

поскольку функция Clock объявлена с оператором деструктуризации как

function Clock({time})

И таких примеров тоже несколько.

Ответить
Дмитрий Елисеев
2021-03-07 14:11

Я по поводу деструктуризации. Она теряет смысл, когда в объявлении функции и при её вызове используется один объект.

Сейчас многие функции-компоненты принимают только один аргумент. Пока мы вызываем функции вручную мы можем передавать его просто без деструктуризации.

Но вскоре мы перейдём на JSON-формат описания виртуального дерева и синтаксис JSX. Там мы со всеми компонентами будем работать уже универсально атоматически, а не вручную. И там будет удобнее передавать один объект props, чтобы не путаться в числе параметров и их порядке.

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

Ответить
Tema Ovchinnikov
2022-11-23 00:27

Интересно получается

let state = {
  time: new Date(),
  lotsData: [
    { id: 1, title: 'Apple', description: 'Apple description', price: '16' },
    { id: 2, title: 'Orange', description: 'Orange description', price: '41' },
  ],
};

function Logo() {
  return '<img class="logo" src="img-logo.png" alt="" />';
}

function Header() {
  return '<header class="header">${Logo()}</header>';
}

function Clock({ time }) {
  return '<div class="clock">${time.toLocaleTimeString()}</div>';
}

function Lot(lotData) {
  return (
    `<article class="lot">
      <div>${lotData.price}</div>
      <h1>${lotData.title}</h1>
      <p>${lotData.description}</p>
    </article>'
  );
}

function Lots({ lotsData }) {
  return lotsData.map((lotData) => '<div class="lots">${Lot(lotData)}</div>');
}

function App({ state }) {
  return '<div class="app">${Header()} ${Clock({ time: state.time })} ${Lots({
    lotsData: state.lotsData,
  })}</div>';
}

function renderDom(realDom, virtualDom) {
  realDom.innerHTML = virtualDom;
}

function render({ state }) {
  renderDom(document.getElementById('root'), App({ state }));
}

render({ state });

setInterval(() => {
  state = {
    ...state,
    time: new Date(),
  };
  render({ state });
});
Ответить
Yevhenii
2025-06-20 06:57

Здравствуйте. Даже не знаю жив ли сайт, но все же спрошу. Если я начал только щупать реакт, этот курс сейчас будет актуален в 2025 или уже устарел? Спасибо.

Ответить
Anton
2026-02-21 05:19

Дмитрий - ты лучший!

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий

Или войти через:

Yandex
MailRu
GitHub
Google