“在命令行里工作很酷,会让你感觉自己像黑客电影里的英雄。”
此言不虚!但是,面对单调乏味的文字瀑布,或者令人望而生畏的命令行参数、美元符号前缀,以及各种莫名其妙的错误信息,我们也难免会感到力不从心。
好在,终端世界里除了冰冷的命令行,还有生机勃勃的 TUI(终端用户界面)。TUI 就像一杯珍珠奶茶,在复古的外表下,蕴藏着现代化的体验。而在 Go 语言的世界里, Bubble Tea
库的出现,让制作 TUI 应用变得像泡一杯奶茶一样简单。
🤔 为什么选择 Bubble Tea?
想象一下,你是一位经验丰富的茶饮师,想要调制一杯独一无二的珍珠奶茶。这时,你会选择什么样的工具呢?
Bubble Tea
就如同一个功能强大的工具箱,它提供了以下几个不可抗拒的优势:
- 熟悉的 Elm 架构: 如果你已经习惯了 React、Vue 或 Elm 等前端框架,那么
Bubble Tea
的 Elm 架构对你来说一定不会陌生。这种架构就像奶茶的配方,清晰易懂,让你轻松上手。 - 模块化的代码组织: Elm 架构不仅易于理解,而且非常适合构建模块化的 UI 代码。你可以从一个简单的应用开始,逐步添加功能,就像在奶茶中加入珍珠、椰果一样,让你的应用逐渐丰满起来。
- 简洁易懂的 Go 语言: Go 语言以其简洁一致的语法著称,这使得阅读和理解他人的代码变得轻而易举。就像品尝一杯精心调制的奶茶,你能清晰地感受到每一种成分的味道。
🚀 从 Hello World 开始
俗话说,万事开头难。让我们从一个简单的 “Hello World” 应用开始,体验一下 Bubble Tea
的魅力吧!
首先,创建一个名为 code-journal
的目录,并在终端中运行以下命令:
go mod init
go get github.com/charmbracelet/bubbletea
然后,创建一个名为 app.go
的文件,并添加以下代码:
package main
import (
tea "github.com/charmbracelet/bubbletea"
)
func main() {
p := tea.NewProgram(
newSimplePage(
"This app is under construction",
),
)
if err := p.Start(); err != nil {
panic(err)
}
}
接下来,创建另一个名为 simple_page.go
的文件,其中包含我们的第一个 UI,一个只显示一些文本的简单页面:
package main
import (
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
// MODEL DATA
type simplePage struct {
text string
}
func newSimplePage(text string) simplePage {
return simplePage{
text: text,
}
}
func (s simplePage) Init() tea.Cmd {
return nil
}
// VIEW
func (s simplePage) View() string {
textLen := len(s.text)
topAndBottomBar := strings.Repeat("*", textLen+4)
return fmt.Sprintf(
}
// UPDATE
func (s simplePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
case tea.KeyMsg:
switch msg.(tea.KeyMsg).String() {
case "ctrl+c":
return s, tea.Quit
}
}
return s, nil
}
在终端中运行以下命令,编译并运行我们的应用:
go build ./code-journal
你将会看到如下输出:
************************
* This app is under construction *
************************
Press Ctrl+C to exit
恭喜你!你已经成功运行了你的第一个 Bubble Tea
应用。
🔬 剖析代码
现在,让我们像品尝奶茶一样,细细品味一下这段代码。
🧋 Model
:Bubble Tea 的核心
main
函数通过创建一个新的 simplePage
模型来启动程序。
p := tea.NewProgram(
newSimplePage(
"This app is under construction",
),
)
我们调用了 tea.NewProgram
函数,它的函数签名如下:
func NewProgram(initialModel Model) *Program
然后调用该程序的 Start
方法来启动我们的应用程序。但是 initialModel
是什么呢?
Model
是 Bubble Tea
的核心接口,它定义了三个方法:
type Model interface {
Init() Cmd
Update(msg Msg) (Model, Cmd)
View() string
}
Init()
方法在应用程序启动时被调用,并返回一个tea.Cmd
。Cmd
可以理解为 “幕后发生的事情”,例如加载数据或计时器。在本教程中,我们没有任何后台操作,因此Init
方法只返回nil
。
func (s simplePage) Init() tea.Cmd {
return nil
}
View()
方法是Bubble Tea
中一个很酷的抽象,它将整个 UI 的显示都表示为一个字符串!在View
方法中,你需要构建并返回这个字符串。
func (s simplePage) View() string {
textLen := len(s.text)
topAndBottomBar := strings.Repeat("*", textLen+4)
return fmt.Sprintf(
}
在上面的代码中,我们将 simplePage
的文本放在一个由星号组成的盒子里,并在底部显示一条消息 “Press Ctrl+C to exit”。
Update()
方法负责处理用户输入。
func (s simplePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
case tea.KeyMsg:
switch msg.(tea.KeyMsg).String() {
case "ctrl+c":
return s, tea.Quit
}
}
return s, nil
}
Update()
方法接收一个 tea.Msg
并返回一个新的 tea.Model
,有时还会返回一个 tea.Cmd
(例如,如果某个操作导致检索数据或计时器启动)。
tea.Msg
的类型签名如下:
type Msg interface{}
所以它可以是任何类型,并携带你需要的数据。它有点像 JavaScript 中的浏览器事件;计时器事件不携带任何数据,点击事件会告诉你点击了什么,等等。
我们正在处理的消息类型是 tea.KeyMsg
,它表示键盘输入。我们检查用户是否按下了 Ctrl+C,如果是,则返回 tea.Quit
命令,该命令属于 tea.Cmd
类型,并告诉 Bubble Tea
退出应用程序。
对于任何其他类型的输入,我们不做任何处理,只是按原样返回模型。但是,如果我们正在执行 UI 导航等操作,则会更改模型上的一些字段,然后返回它,从而导致 UI 更新。
⏭️ 下一站:菜单组件
在下一篇文章中,我们将学习如何创建一个菜单组件,并让我们的应用程序更加生动有趣!敬请期待!