Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Инициализация приложения JS: Архитектура фронтенда

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

  • Инициализацию
  • Исполнение
  • Завершение

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

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

Что входит в инициализацию? Все, что нужно сделать ровно один раз для последующего использования в приложении:

  • Создание начального состояния
  • Настройка i18next
  • Загрузка и запуск фреймворка, если он есть
  • Подключение и настройка различных библиотек: http-клиенты, вебсокеты, работа с датами и так далее

Этот список далеко не полный, в каждой конкретной ситуации в инициализацию попадает что-то свое.

На практике процесс инициализации организуется так: создается функция, внутри которой выполняется вся необходимая настройка. Для удобства сделать это можно в файле init.js.

// Гипотетический пример
// файл init.js
import i18next from 'i18next';
import io from 'socket.io-client';

// Начальная функция
export default async () => {
  // создание экземпляра i18next
  const i18nextInstance = i18next.createInstance();
  await i18nextInstance.init({
    lng: 'ru',
    resources: /* переводы */
  });

  const state = {
    /* описание состояния */
  }

  // создание сокета
  const socket = new io();
  socket.on(/* настройка вебсокетов */);

  const form = document.querySelector('some-form');
  form.addEventListener('submit', (e) => {
   // А вот тут логика приложения, ее можно вынести в отдельную функцию или несколько функций в отдельном модуле
   // где-то в таких обработчиках используется state и socket
  });
};

Запуск этой функции происходит уже в другом месте, например, в файле index.js, который является точкой входа в приложение:

import runApp from './init.js';

runApp();

Почему разделение именно такое? Во фронтенде приложение, как правило, глобально. То есть оно одно на всю страницу, грузится ровно один раз и один раз запускается и управляет всем происходящим так, как будто вокруг больше ничего нет (других приложений, о которых нужно думать). Но так бывает не всегда. Например, виджеты могут появляться на одной странице более одного раза, а значит каждый виджет должен быть вещью «в себе». То есть инициализация такого виджета работает в своем собственном окружении со своими собственными объектами и не меняет ничего вне (глобальные объекты). Иначе возникнут конфликты, и один виджет будет мешать другому.

С другой стороны, в тестировании каждый тест строится так, что он не зависит от других тестов, то есть каждый тест работает так, как будто других тестов не существует. Такое поведение требует инициализации приложения для каждого теста «с нуля». Только в этом случае можно гарантировать, что изменения состояния приложения, сделанные в одном тесте не повлияют на другие тесты. Яркий пример — инициализация i18next. Эта библиотека экспортирует глобальный объект, который можно инициализировать только один раз, повторные инициализации того же объекта (например, при повторных запусках приложения в тестах) приведут к багам и запрещены документацией. По этой причине в примере выше каждый старт приложения создает свой собственный экземпляр i18next, который затем прокидывается в использующие его функции.

// Подробно тесты изучаются в других курсах
// Где-то в тестах
import runApp from '../src/init.js';

// Эта функция выполняется перед каждым тестом
beforeEach(() => {
  runApp(); // инициализация
});

test(/* тут тесты */)
test(/* тут тесты */)

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


Дополнительные материалы

  1. Состояние на уровне модулей
  2. Скрипты, модули и библиотеки
  3. Что такое веб-сокеты и как они вообще работают

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 28 ноября
профессия
от 39 525 ₸ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 28 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»