Bubble Tea — TUI-фреймворк для Go. Мои открытия и ошибки
- суббота, 23 августа 2025 г. в 00:00:08
Недавно я наткнулся на Bubble Tea — терминальный UI-фреймворк для Go, и буквально влюбился в то, как он отрисовывает интерфейсы в консоли. В репозитории есть множество примеров — и выглядят они действительно красиво.
Я никак не связан с разработчиками — просто захотел поделиться личным опытом.
С первого взгляда Bubble Tea выглядит как высокоуровневый фреймворк с красивой абстракцией. Но под капотом он довольно низкоуровневый в плане контроля, и построен на архитектуре, вдохновлённой ELM.
Она основана на трёх основных концепциях:
Model — структура, содержащая всё состояние приложения (например, позицию курсора, список элементов, ввод пользователя и т. д.).
Update(msg) — функция, в которой можно изменять модель. Принимает сообщение (пользовательское действие или внутреннее событие), возвращает обновлённую модель и (опционально) команду.
View(model) — рендеринг интерфейса на основе текущей модели.
Что такое команды?
Команды — это события, которые мы можем генерировать и отправлять сами. Это может быть как пользовательское действие (например, нажатие клавиши), так и произвольная внутренняя структура. В Update
они обрабатываются через type switch
.
Пример: бесконечный спиннер загрузки
Допустим, нам нужен спиннер с тремя кадрами, которые циклично меняются каждую секунду. Вот как это реализуется в Bubble Tea:
В модели храним текущий индекс кадра и список кадров.
Объявляем кастомную команду type nextFrame struct {}
.
При инициализации модели вызываем первую команду nextFrame
.
В Update
, при получении команды nextFrame
, увеличиваем индекс и планируем следующую команду с задержкой в 1 секунду (с помощью утилит из фреймворка).
Фреймворк сам вызывает View
при изменениях, а мы в View
просто показываем нужный кадр по индексу.
В результате — плавно анимированный спиннер, без явных таймеров и горутин.
Команды оказались удивительно мощным инструментом. Например, предположим, нам нужно загрузить 10 изображений с помощью 3 воркеров. В обычном Go мы бы использовали каналы и горутины, но в Bubble Tea можно обойтись всего одной командой:
Команда проверяет: есть ли ещё изображения в очереди. Если да — загружает, сохраняет результат, вызывает себя снова.
При старте запускаем 3 таких команды — и получаем 3 параллельно работающих воркера.
Когда очередь заканчивается, команды просто перестают запускаться.
Это не "классическая" конкурентность Go, но она естественно вписывается в архитектуру UI-приложения, и позволяет сохранять отзывчивость интерфейса.
Как только вы начнёте писать что-то чуть сложнее Hello World, всё ваше приложение станет ELM-подобным. Другого пути нет: либо вы принимаете архитектуру, либо постоянно боретесь с ней. Вот что помогло мне на этом пути:
Не полагайтесь на нейросети для генерации кода. Большинство LLM плохо справляются с архитектурой Bubble Tea. Код получается нечитаемым. Лучше продумать архитектуру, модель и структуру проекта самостоятельно, а LLM подключать точечно — например, для генерации вспомогательных функций или отладки.
Разделяйте ответственность. Выносите View
, Update
и Model
в отдельные файлы. Даже официальные примеры временами тяжело читать без этого.
Следите за читаемостью Update
. Он быстро может превратиться в 300 строк кода. Используйте вспомогательные методы и выносите обработку отдельных команд.
Выносите стили в отдельный файл. Цвета, отступы, рамки — всё это лучше оформить централизованно.
Обязательно проводите рефакторинг. Написали рабочую реализацию — сделайте её читаемой. Иначе завтра вы сами себя не поймёте.
Эти советы в целом универсальны, но именно в Bubble Tea их игнорирование быстро делает поддержку невозможной.
Я реализовал визуальный менеджер зависимостей для go.mod
, который сканирует зависимости и показывает, какие можно и нужно обновить. Это может быть полезно, потому что стандартный go get -u
просто обновляет всё подряд, и часто приводит к тому, что проект перестаёт собираться.
Проект здесь: chaindead/modup
Если у вас был опыт с Bubble Tea — расскажите, как оно? Какие архитектурные приёмы вам помогли?