Разрабатываем React-компоненты многократного использования

    В прошлом году мы рассказывали, как устроен фронтенд в Программе «Единая фронтальная система», о библиотеке элементов и нашем подходе к переиспользованию 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.

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

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

      • 0
        Попробуйте оформлять паки компонент, в которых отдельно описываете состояние и поведение.

        Плюс вы можете использвоать эмуляторы view port a la такое addon-viewport
      • –5

        Меня часто спрашивают, как же подобная задача решается в $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

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


        Заголовок спойлера
        tasks() : $mol_app_todomvc_task[] {
            return this.$.$mol_http.resource( 'tasks.json' ).json() 
        }
        
        task_ids() {
            return Object.keys( this.tasks() )
        }
        
        task( id : number ) {
            return this.tasks()[ id ]
        }

        Или что-нибудь по сложнее — будем и читать и писать в файл, через WebDav:


        Заголовок спойлера
        tasks( next? : $mol_app_todomvc_task[] ) {
            return this.$.$mol_webdav.item( 'tasks.json' ).json( next )
        }
        
        task_ids( next? : number[] ) {
            return Object.keys( this.tasks( next ? next.map( id => this.task( id ) ) : undefined ) )
        }
        
        task( id : number , next? : $mol_app_todomvc_task ) {
            return this.tasks( next )[ id ]
        }

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

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

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

          • 0

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

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

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

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

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

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

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