React для терминала на Rust с flexbox и хуками
Если вы хоть раз писали консольный интерфейс на Rust, вы наверняка брали Ratatui. Это надежная библиотека, на которой держатся десятки отличных утилит. Но как только проект выходит за рамки пары кнопок, начинается знакомая рутина: нужно вручную делить экран на прямоугольники через Layout, высчитывать проценты и отступы, прокидывать события через гигантские конструкции match. В веб-разработке мы давно привыкли к компонентному подходу и flexbox, а в терминале до сих пор часто пишем так, будто на дворе 1998 год.
Недавно наткнулся на крейт iocraft, автор которого решил эту проблему кардинально. Он перенес опыт React, SwiftUI и Ink прямо в мир системного программирования на Rust.
Знакомая компонентная модель
В основе библиотеки лежит макрос element!. Он собирает дерево элементов через синтаксис, сильно напоминающий JSX или декларативный Swift. Никаких ручных расчетов координат. За раскладку отвечает движок taffy, который честно реализует спецификацию flexbox для Rust.
Вот как выглядит классический Hello World:
use iocraft::prelude::*;
fn main() {
element! {
View(
border_style: BorderStyle::Round,
border_color: Color::Blue,
) {
Text(content: "Hello, world!")
}
}
.print();
}
Мы просто оборачиваем Text в контейнер View, вешаем скругленную синюю рамку и вызываем .print(). Результат улетает в стандартный вывод без необходимости захватывать терминал в полноэкранный режим. Это удобно, если вам нужен не интерактивный дашборд, а аккуратно отформатированный вывод консольной команды или логов.
Хуки и состояние внутри терминала
На статической верстке сходство с фронтендом не заканчивается. Для динамических интерфейсов автор добавил макрос #[component] и привычные хуки жизненного цикла.
В коде ниже компонент Counter держит собственное локальное состояние через use_state и запускает фоновый асинхронный таймер через use_future:
use iocraft::prelude::*;
use std::time::Duration;
#[component]
fn Counter(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
let mut count = hooks.use_state(|| 0);
hooks.use_future(async move {
loop {
smol::Timer::after(Duration::from_millis(100)).await;
count += 1;
}
});
element! {
Text(color: Color::Blue, content: format!("counter: {}", count))
}
}
fn main() {
smol::block_on(element!(Counter).render_loop()).unwrap();
}
Когда значение count меняется, iocraft запускает перерисовку нужной части экрана. При этом движок оптимизирует передачу пропсов: контекст и параметры передаются по ссылкам, поэтому лишних аллокаций памяти и клонирования тяжелых структур здесь нет.
На что способна библиотека на практике
Возможностей встроенных примитивов хватает для сборки интерфейсов любой сложности. В примерах репозитория лежат готовые рецепты для самых частых задач:
- Полноэкранные приложения с вводом текста и обработкой фокуса
- Многоколоночные адаптивные таблицы
- Интерактивные формы с валидацией
- Калькуляторы и оверлеи с плавающими модальными окнами
Посмотрим, как выглядит готовая верстка сложных элементов:
Стилизация кроссплатформенная. Приложения одинаково корректно рендерятся и в стандартных терминалах Linux/macOS, и в Windows PowerShell.
Стоит ли переходить с Ratatui
Если у вас уже написан сложный терминальный клиент на Ratatui, переписывать его с нуля нет особого смысла. Ratatui взрослее, вокруг него сформировалось большое сообщество и написаны десятки сторонних виджетов.
Но если вы стартуете новую CLI-утилиту, пишите внутренний инструмент для команды или просто устали вручную считать строки и колонки терминала, iocraft экономит массу времени. Декларативный синтаксис делает код компактнее, а flexbox избавляет от головной боли при изменении размеров окна.
Проект распространяется под двойной лицензией MIT / Apache-2.0. Документация на docs.rs достаточно подробная, а исходники примеров в репозитории наглядно показывают, как собирать собственные компоненты.
