Steroids — ещё один фронтенд фреймворк на базе React?

от автора

Фреймворк Steroids

Фреймворк Steroids

На старте проекта обычно встает вопрос о выборе готовой ui-библиотеки для решения шаблонных задач, таких как создание форм, инпутов, кнопок и других компонентов. Количество готовых ui-библиотек для React так стремительно растет, что уже сложно остановить свой выбор на какой либо из них. Зато в таком разнообразии каждый может найти библиотеку, подходящую под его задачи. В этой статье хочется рассказать о фреймворке Steroids, который разработан и поддерживается в нашей компании.

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

Что же такое Steroids и для каких задач он может подойти?

Steroids — это фреймворк, который задает архитектуру для всего проекта, а также содержит набор как простых, так и комплексных ui-компонентов, инструменты для взаимодействия с бэкендом и работой с формами, компоненты для работы с хранилищем браузера, websocket и другими технологиями.

Фреймворк Steroids подходит для создания полноценного SPA приложения с продуманной архитектурой, которое содержит в себе:

  • роутинг и управление состоянием, в том числе с использованием Redux, React Context;

  • взаимодействие с сервером — отправка запросов, обработка ошибок;

  • работу с формами — валидация, отправка данных на сервер, сохранение данных полей формы в state manager;

  • загрузку и отображение данных в виде списка или таблицы, сортировка, пагинация, фильтрация данных;

  • локализацию всего приложения;

  • использование серверного рендеринга приложения (SSR).

Основные фичи и особенности фреймворка

У фреймворка Steroids много особенностей и фич, о которых стоит рассказать. Всё это точно не влезет в одну статью. Поэтому здесь пойдет речь про некоторые из них:

  • фреймворк задаёт определенную структуру проекта;

  • все ui-компоненты разделены на core и view части;

  • во фреймворк входят не только ui-компоненты, но  и логические компоненты, которые «под капотом» используют другие инструменты или библиотеки.

Структура проекта

Фреймворк Steroids задаёт определенную структуру папок и файлов проекта. Эта структура предполагает отдельные директории для переиспользуемых компонентов, редьюсеров и экшенов, роутов приложения, стилей, модальных окон и так далее. Ниже приведен пример такой структуры:

├── node_modules ├── public ├── src │   ├── actions │   ├── reducers │   ├── modals │   ├── routes │   │   └── IndexPage │   │       └── views │   │           ├── MyBox.ts │   │           └── MyBox.scss │   │       ├── IndexPage.ts │   │       ├── IndexPage.scss │   │       └── index.ts │   ├── shared │   │   └── Layout │   │       ├── Layout.ts │   │       ├── Layout.scss │   │       └── index.ts │   ├── ui │   │   └── form │   │       ├── Button │   │       │   ├── ButtonView.tsx │   │       │   └── ButtonView.scss │   │       └── CustomField │   │           ├── CustomField.ts │   │           ├── CustomFieldView.tsx │   │           └── CustomFieldView.scss │   ├── style │   │   ├── index.scss │   │   └── variables.scss │   ├── Application.tsx │   └── index.tsx ├── .eslintrc ├── index.d.ts ├── package.json ├── tsconfig.json ├── webpack.js └── yarn.lock

Созданное на основе Boilerplate приложение имеет нужную структуру, в нём созданы все необходимые папки и файлы. Оно представляет собой тривиальное SPA-приложение с единственной страницей, и сразу после развертывания готово к запуску. Это приложение можно наполнить своими страницами и компонентами, располагая их в соответствующих структуре папках.

Разделение логики и отображения компонентов

Чтобы логика работы компонента была отделена от его отображения, каждый ui-компонент разделён на core и view часть.

Core — ядро компонента. Оно содержит всю бизнес-логику компонента, принимает и обрабатывает полученные данные из props, находит или получает нужную «вьюшку» (view-часть компонента) и прокидывает в неё подготовленные данные. Core-компонент практические не содержит jsx кода и обращений напрямую к DOM.

View — отображение компонента. Это часть, которая отвечает за непосредственную отрисовку компонента. Как правило она содержит только jsx код и scss стили компонента, без бизнес-логики. При кастомизации компонента его View.tsx и/или View.scss файлы при необходимости могут полностью копироваться из фреймворка Steroids и изменяться под дизайн конкретного проекта.

Если при разработке достаточно стандартного отображения компонента, то его можно просто импортировать из core-части фреймворка Steroids (npm-пакет @steroids/core) и использовать в нужном месте. Если же есть потребность как-то кастомизировать компонент под дизайн проекта, то можно воспользоваться несколькими способами, один из которых работает как раз благодаря разделению компонента на core- и view-части.

Вот три основных способа кастомизации:

  1. Передача через пропсы css-классов или объекта style.

  2. Переопределение css-переменных.

  3. Переопределение view-части компонента — его стилей или tsx-кода.

Как использовать первые два способа, думаю, понятно. Они есть в большинстве готовых ui-библиотек. Поэтому перейдем к примерам того, как можно переопределить view часть.

Первый и самый простой способ — передать кастомное отображение через проп view:

const CustomInputFieldView = (props: IInputFieldViewProps) => (    <div>        <span>Кастомное отображение инпута</span>        <input            {...props.inputProps}            placeholder={props.placeholder}            disabled={props.disabled}            required={props.required}        />    </div> );  const MyForm = () => (    <Form>        {/* инпут со стандартным отображением */}        <InputField attribute='name' />        {/* инпут с кастомным отображением */}        <InputField            attribute='surname'            view={CustomInputFieldView}        />    </Form> );

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

Второй способ заключается в полной замене стандартных view-частей компонентов на кастомные. В этом случае «вьюшки» применяются ко всем компонента сразу, например ко всем Button или ко всем Form.

Файлы со стилями компонентов подключаются в src/style/index.scss через конструкцию @use, например:

@use '~@steroidsjs/bootstrap/form/InputField/InputFieldView';

А файлы с tsx-кодом компонента в src/ui/bootstrap.ts, например:

'form.InputFieldView': {    lazy: () => require('@steroidsjs/bootstrap/form/InputField/InputFieldView').default },

Чтобы переопределить view-часть, достаточно изменить пути к файлам — к scss-файлу или tsx-файлу, смотря что нужно кастомизировать.

Логические компоненты

Одна из особенностей Steroids состоит в том, что «из коробки» доступны не только ui-компоненты, но и логические компоненты или, по другому, компоненты-обертки над другими инструментами и библиотеками. Каждый компонент представляет из себя класс с набором свойств и методов. 

Инициализируются эти компоненты в проекте в файле src/Application.tsx в константе config в поле components:

export const config = {    reducers: require('./reducers').default,    routes: () => require('routes').default,    layoutView: () => require('shared/Layout').default,    screen: {},    theme: {},    components: {        locale: LocaleComponent,        http: HttpComponent,        resource: ResourceComponent,    }, }

В этом же конфиге можно задать свойства для компонентов, например:

components: {    http: {       className: HttpComponent,       apiUrl: 'https://steroids.dev/',    }, },

Используются такие компоненты внутри обычных react компонентов, а получить их можно из хука useComponents. Пример:

export default function Header() {    const bem = useBem('Header');    const {http} = useComponents();       const [user, setUser] = React.useState(null);     const getUser = () => http.get('api/v1/user').then(      response => setUser(response.data)    );       return (<>...</>) }

В Steroids реализовано более десяти таких компонентов. Вот основные из них:

HttpComponent — используется для отправки запросов на бекенд, обработки ошибок, авторизации. Поддерживает авторизацию через токен. «Под капотом» использует библиотеку axios.

JwtHttpComponent — расширяет HttpComponent, добавляя функционал обновления токена авторизации.

LocaleComponent — локализует приложение, поддерживает конфигурацию языка и временной зоны.

ClientStorageComponent — работает с хранением данных в браузере (cookie, local/session storage).

WebSocketComponentсоздаёт websocket-соединения и управляет ими

UI-Kit, простые и комплексные компоненты

В Steroids ui-компоненты условно делятся на простые и комплексные.

Простые компоненты — компоненты интерфейса, которые можно использовать независимо от других компонентов, например кнопка, форма, список.

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

Все компоненты в UI-Kit фреймворка Steroids разделены на несколько групп:

  • content;

  • form;

  • layout;

  • list;

  • nav;

  • typography.

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

Вот примеры простых ui-компонентов из Steroids, которые наиболее часто используются в проектах:

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

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

Button — кнопка или ссылка. Используется в интерфейсе для выполнения какого-либо действия по клику.

InputField — поле ввода текста.

NumberField — числовое поле ввода.

FieldList — список из сгруппированных полей формы.

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

Form — компонент для создания формы. Предоставляет управление и синхронизацию состояния формы, а также позволяет выполнять отправку данных формы на сервер с возможностью валидации и обработки результатов.

Notifications — компонент, представляющий собой контейнер для отображения всплывающих уведомлений.

Tooltip — компонент, предоставляющий всплывающую подсказку для дочерних элементов.

Grid — представление данных коллекции в виде таблицы.

List — компонент для представления коллекции в виде списка.

Nav — компонент навигации позволяет переключаться между группами связанного контента.

Комплексные компоненты используются реже, так как реализуют функционал, заточенный под узкий круг сложных задач. 

Вот примеры некоторых компонентов:

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

CalendarSystem

CalendarSystem

Kanban — компонент для работы со списком карточек, сгруппированных в списки. Поддерживает функционал создания и редактирования карточек, добавления к ним тегов и описания, открытия карточек в детальном виде — в модальном окне.

Kanban

Kanban

Chat — реализует функционал чата, поддерживает загрузку данных при отправке сообщений, просмотр истории сообщений, разделение сообщений по датам, прикрепление файлов к сообщениям.

Chat

Chat

Остальные простые и комплексные компоненты UI-Kit Steroids можно посмотреть по ссылке — https://steroids.dev/ru/docs/ui.

Сравнение с другими библиотеками

Мы решили сравнить функционал разных ui-библиотек для React, которые пользуются популярностью. Для сравнения выбрали несколько пунктов, которые считаем важными при выборе библиотеки:

  • наличие UI-Kit;

  • готовая архитектура для проекта;

  • подключенные «под капотом» базовые зависимости для создания SPA приложения;

  • работа с формами;

  • работа со списками (в том числе пагинация и фильтрация);

  • работа с роутингом;

  • работа с SSR;

  • возможность кастомизировать компоненты;

  • наличие активного сообщества.

Для сравнения мы выбрали несколько библиотек:

  • Material UI;

  • Ant Design;

  • Mantine;

  • Semantic UI React;

  • Steroids.

Вот что у нас получилось:

Сравнительная таблица библиотек

Сравнительная таблица библиотек

У всех выбранных библиотек есть Ui-Kit и возможность разными способами кастомизировать компоненты. А вот с остальными критериями не все так гладко.

Готовая архитектура проекта, настроенный роутинг и подключенные «под капотом» зависимости есть только у библиотек Material UI, Mantine и Steroids. При чем у Material UI такие функции доступны только в платных шаблонах.

Работа с SSR есть только у двух библиотек — Mantine и Steroids. У первой «под капотом» используется Next.js, а у второй написана собственная реализация SSR, которая подключается через npm-пакет @steroids/ssr.

Посмотрим как обстоят дела с компонентами. В каждой из приведенных библиотек есть компоненты для полей формы, но только в двух из них (все те же Mantine и Steroids) «под капотом» реализована логика работы с формами, а именно валидация значений, обработчик onSubmit, режимы контролируемой и неконтролируемой формы.

Вот пример использования форм, взятый из документации библиотеки Mantine:

import { Button, Checkbox, Group, TextInput } from '@mantine/core'; import { useForm } from '@mantine/form';  function Demo() {    const form = useForm({       mode: 'uncontrolled',       initialValues: {       email: '',       termsOfService: false,    },    validate: {       email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),    },    });     return (       <form onSubmit={form.onSubmit((values) => console.log(values))}>         <TextInput            withAsterisk            label="Email"            placeholder="your@email.com"            key={form.key('email')}            {...form.getInputProps('email')}         />         <Checkbox            mt="md"            label="I agree to sell my privacy"            key={form.key('termsOfService')}            {...form.getInputProps('termsOfService', { type: 'checkbox' })}         />         <Group justify="flex-end" mt="md">            <Button type="submit">Submit</Button>         </Group>     </form>   ); }

И вот пример, как формы используются в Steroids:

import {login} from '@steroidsjs/core/actions/auth'; import {ROUTE_USERS} from 'routes';  const FORM_ID = 'LoginFrom';  export default function LoginFrom() {      return (         <div className={bem.block()}>             <Form                 formId={FORM_ID}                 fields={formFields}                 action='/api/v1/auth/login'                 actionMethod='POST'                 useRedux                 onComplete={(values, data) => {                     if (data.accessToken) {                         dispatch(login(data.accessToken, ROUTE_USERS));                     }                 }}                 submitLabel='Войти'             />         </div>     ); }

В этом примере реализована даже логика отправки данных формы на конкретный API endpoint. У Form также есть такие пропсы, как onBeforeSubmit, onAfterSubmit, onComplete, которые вызывают функции соответственно до отправки формы, после отправки и в случае успешного выполнения запроса. Еще есть пропсы для сохранения данных формы в редаксе (useRedux), в адресной строке (addressBar) и в local storage (autoSave). Вообще компонент Form в Steroids довольно функциональный. О нем будет одна из следующих статей.

В работе со списками данных похожая ситуация. Во всех фреймворках есть компоненты List и Pagination, но в большинстве случае это только визуальная составляющая, для работы которой нужно дописывать логику уже в проекте. А в Steroids такая логика написана «под капотом». 

Вот пример, как используется List c пагинацией:

<List    listId='FlightList'    items={flightItems}    itemView={ListCard}    itemProps={{       bem,    }}    contentClassName={bem.element('list')}    paginationSize={{       defaultValue: 2,       sizes: [1, 2, 3],    }}    pagination={{       defaultValue: 3,    }} />

Теперь давайте сравним степень кастомизации компонентов в разных библиотеках. Везде поддерживается кастомизация через различные пропсы для стилей (в каждой библиотеке это будут свои) и через переопределение css-переменных. Библиотека Mantine также предоставляет возможность использовать ее как headless ui-библиотеку, что значит использовать только логику (core-часть), а стили добавлять свои. В Steroids есть возможность переопределять полностью view-часть компонента. Это ещё более глубокая степень кастомизации. Подобный функционал мы встречали только в одной библиотеке — Korus UI от Сбера. Там есть возможность передать view-часть через пропсы, но нет возможности переопределить ее для всех компонентов в проекте (например, для всех InputField).

И напоследок сравним библиотеки по наличию активного сообщества. Этот критерий тоже имеет вес при выборе ui-библиотеки для проекта. У трех из пяти библиотек есть сообщество разработчиков, которое занимается  поддержкой, развитием и фиксом багов. А именно у Material UI, Mantine и Steroids.

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

И в заключение

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

В 2022 году мы выиграли грант от faise на его развитие. В рамках работ по гранту было реализовано много новых ui-компонентов, а также несколько комплексных компонентов. Мы провели рефакторинг кода, покрыли фреймворк тестами на 70%, написали подробную документацию и запустили сайт с большим набором демоверсий компонентов.

Если вас заинтересовал фреймворк Steroids и вы хотите узнать о нем больше, то вот ссылка на документацию и быстрый старт

Спасибо за внимание)


ссылка на оригинал статьи https://habr.com/ru/articles/927546/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *