В прошлом году мы рассказывали, как устроен фронтенд в Программе «Единая фронтальная система», о библиотеке элементов и нашем подходе к переиспользованию UI компонентов. Сегодня предлагаем сравнить несколько подходов к разработке React-компонентов, спасибо Cory House за отличный материал!



React – это библиотека для создания компонентов. Он позволяет разбить пользовательский интерфейс на составляющие. Вопрос лишь в том, насколько детализированы эти элементы.

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

Получается, что приложение ToDo теперь нужно запускать двумя способами:

1. Самостоятельно;
2. В рамках приложения для выставления счетов.

Как лучше поступить в такой ситуации?



Для запуска React-приложения из разных точек есть три варианта:

1. iframe? — внедрение приложения todo в приложение для выставления счетов через iframe.
2. Многократно используемый компонент приложения? — распространение всего приложения todo через npm.
3. Многократно используемый UI компонент — npm-пакет, включающий только разметку приложения.

Рассмотрим преимущества каждого из этих подходов.

Подход 1: iFrame

Самый простой и очевидный подход — использовать iframe, чтобы поместить приложение ToDo в приложение для выставления счетов. Но тут возникают проблемы:

1. Если два приложения отображают одни и те же данные, есть риск нарушения синхронизации этих данных;
2. Если два приложения используют одни и те же данные, в конечном итоге придется делать лишние обращения к API для получения этих данных;
3. Поведение приложения, помещенного в iframe, нельзя изменить;
4. Когда другая команда выпускает приложение, содержащее ваше приложение внутри iframe, это неминуемо затронет и вас.

Итог: Забудьте! iframe вам не нужен.



Не-не-не!

Подход 2: Многоразовый компонент приложения

Распространение приложения через npm, а не через iframe, позволит избежать проблему № 4 из списка выше, остальные же сохранятся — API, авторизация и поведение. Поэтому я не советую публиковать в npm приложение целиком. Уровень детализации слишком высок, что затрудняет взаимодействие с пользователем.



React-компоненты, которые используются многократно, должны быть детализированными и их должно быть легко компоновать — как детали конструктора LEGO.

Подход 3: Переиспользуемые UI компоненты

Есть более детальный подход, основанный на использовании 2-х составляющих:

1. «Глупые» React-компоненты — только интерфейсы и никаких API-вызовов;
2. API-обертки.

«Глупые» React-компоненты можно легко настраивать, объединять и переиспользовать. При использовании «глупых» компонентов подобным образом вы можете предоставлять необходимые данные или определять, какие API-вызовы должно осуществлять приложение.

Однако, если вы собираетесь объединять несколько «глупых» компонентов, нужно подготовить одинаковый API для нескольких приложений. Вот тут как раз понадобятся API-обертки.

Что такое API-обертки? Это javascript-файлы, содержащие HTTP-запросы к вашему API. Для HTTP-запросов можно использовать, к примеру, библиотеку Axios.

Представьте, что у вас есть пользовательский API. Как сделать пользовательскую API-обертку?

1. Создайте js-файл с публичными функциями, например, getUserById, saveUser
и т.п. При этом каждая функция принимает соответствующие параметры и использует Axios/Fetch для отправки HTTP-запросов к вашему API.
2. Создайте npm-пакета userApi, который будет содержать код вашей обертки.

Пример:

	 /* Данная API-обертка удобна, поскольку она: 
           1. Содержит нашу стандартную конфигурацию Axios.
	   2. Абстрагирует логику для определения baseURL.
	   3. Предоставляет понятный, простой в использовании набор JavaScript-функций для взаимодействия с API. Это делает вызовы API краткими и единообразными. 
	
	*/
	import axios from 'axios';
	
	let api = null;
	
	function getInitializedApi() {
	  if (api) return api; // возвращает api, если он уже инициализирован.
	  return (api = axios.create({
	    baseURL: getBaseUrl(),
	    responseType: 'json',
	    withCredentials: true
	  }));
	}
	
	// Вспомогательные функции
	function getBaseUrl() {
	  // Поместите сюда логику для получения baseURL посредством:
	  // 1. Анализа URL для определения окружения, в котором запущено приложение.
	  // 2. Поиска переменной среды как части процесса сборки.
	}
	
	function get(url) {
	  return getInitializedApi().get(url);
	}
	
	function post(url, data) {
	  return getInitializedApi().post(url, data);
	}
	
	// Публичные функции
	// Обратите внимание, насколько короткой получилась эта часть благодаря общей конфигурации и вспомогательным функциям выше. 
	export function getUserById(id) {
	  return get(`user/${id}`);
	}
	
	export function saveUser(user) {
	  return post(`user/${id}`, {user: user});
	}


Распространенной практикой является публикация React-компонентов и API-оберток в npm в виде приватных пакетов, в качестве альтернативы npm может использоваться Artifactory.

Эти «детали LEGO» обеспечивают основу для быстрого создания новых приложений из переиспользуемых элементов.
Система, составные части которой легко компоновать, предоставляет компоненты, которые можно выбирать и соединять в различных сочетаниях для удовлетворения соответствующих требований пользователя.?— ?Википедия

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

С многократно используемыми React-компонентами и API-обертками, опубликованными в npm, легко создать что-то реально крутое — почти как из деталей LEGO.

А какой подход используете вы? Приглашаем обсудить в комментариях к статье.

Комментарии (5)


  1. UbuRus
    19.01.2018 12:05

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


  1. vintage
    19.01.2018 22:41

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


    Итак, берём реализацию ToDoMVC. Всё приложение — это один компонент $mol_app_todomvc: https://github.com/eigenmethod/mol/tree/master/app/todomvc.


    Если заглянуть в код, то можно обнаружить, что работа с задачами ведётся через два свойства:



    Оба свойства полиморфные, то есть через них можно как читать данные, так и писать.


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


    Заголовок спойлера
    <= Todo_widget $mol_app_todomvc
        task_ids?val <=> task_ids?val /
        task!id?val <=> task!id?val *
            title         completed false


  1. babylon
    20.01.2018 03:13

    А теперь, добавим нашу логику. Например, загрузим список задач из json файла:

    Вы изменили tree!!!???
    Я всё жду когда вы перейдете на синтаксис запросов как в E4X https://ru.wikipedia.org/wiki/ECMAScript_%D0%B4%D0%BB%D1%8F_XML. Такие запросы можно перепрограммировать, а не заниматься оборачиваем одной или двух инструкций в функциональную обёртку. Долой функции и классы! Оставляем массивы и группы.


    1. vintage
      20.01.2018 08:17

      В js с json все же работать проще. Хотя, конечно, убивает, что нельзя сказать "у вас ошибка на такой-то строке". Е4х, к сожалению, все.


  1. rusmeloman
    23.01.2018 19:28

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

    В статье приведен пример Задачника (To Do). Если рассматривать банковский процесс с множеством ролей, то это гораздо более сложный механизм презентации информации для пользователя, чем классический задачник, при этом имеет большую зависимость от бизнес логики работы роли. Такой задачник в банковской среде является продуктом бизнес идеи, а следовательно должен проверятся на UX в рамках конкретной роли. Логика работы такого задачника, даже в рамках одной роли, обеспечивается несколькими технологическими сервисами, поэтому невозможно выделить только одну компетенцию для реализации полного решения, что влечет за собой сложное межкомандное взаимодействие и тестирование. Если объединять множество ролей, то потребуется сложная конфигурация решения и возникают ограничения на разработку в инструментальных средствах.

    Для Задачника наиболее оптимальным вижу подход разработки «глупых» компонентов и минимальная сборка в npm. При этом имеет смысл выделить команду, которая будет полностью отвечать за технологические сервисы работы с задачами и всей подложки связанной с этим. Команда заказчика будет собирать лего под непосредственным дизайн и архитектурным контролем.

    Бизнесу Бизнес!