Фреймворк 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 и вы хотите узнать о нем больше, то вот ссылка на документацию и быстрый старт

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

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


  1. nihil-pro
    15.07.2025 06:58

    Шо, опять?


  1. markelov69
    15.07.2025 06:58

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

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

    Вы сделали 11 ошибок в слове плохой. Вместо SPA приложения с плохой архитектурой, вы по ошибке написали с продуманной.

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


    1. gunGarave
      15.07.2025 06:58

      Осильте уже что-то сложнее MobX. Внезапно будет и Redux не плох, если применять его к месту и руками.


    1. isumix
      15.07.2025 06:58

      вы прямо Фьюзор описали

      рендер html и жизненный цикл компонента, а управление состоянием, как глобальным, так и локальным - MobX

      только в нем можно любой стейт менеджер подключить на пропсу { mount }

      ну и раз нет состояния - то становится не нужен ре-рендеринг и хуки, жизненный цикл компонента сжимется в 4 состояния: create, mount, update, unmount


  1. xadd
    15.07.2025 06:58

    Опять тут навязывают всякие структуры, чтобы говнокод случайно не написали, сделайте просто, чтобы собиралось без геморроя и без всяких топорных /app & /pages


    1. affka
      15.07.2025 06:58

      Структуры никто не навязывает, но есть описание и примеры как можно делать (и которые мы у себя используем как правила). Но привязки к директориям (app & /pages ...) здесь нет

      upd: и да, если не дать примера как делать — будут делать говнокод :) Либо прогеры будут крутые, но каждый напишет по своему.. и опять получится говнокод)


      1. xadd
        15.07.2025 06:58

        А, ну тогда совсем хорошо