「åœ¨å‘½ä»¤è¡Œé‡Œå·¥ä½œå¾ˆé…·ï¼Œä¼šè®©ä½ æ„Ÿè§‰è‡ªå·±åƒé»‘客电影里的英雄。」
æ¤è¨€ä¸è™šï¼ä½†æ˜¯ï¼Œé¢å¯¹å•è°ƒä¹å‘³çš„æ–‡å—瀑布,或者令人望而生ç•çš„å‘½ä»¤è¡Œå‚æ•°ã€ç¾Žå…ƒç¬¦å·å‰ç¼€ï¼Œä»¥åŠå„ç§èŽ«å其妙的错误信æ¯ï¼Œæˆ‘们也难å…会感到力ä¸ä»Žå¿ƒã€‚
好在,终端世界里除了冰冷的命令行,还有生机勃勃的 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("%s\n* %s *\n%s\n\nPress Ctrl+C to exit", topAndBottomBar, s.text, topAndBottomBar)
}
// 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("%s\n* %s *\n%s\n\nPress Ctrl+C to exit", topAndBottomBar, s.text, topAndBottomBar)
}
在上é¢çš„代ç ä¸ï¼Œæˆ‘们将 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 更新。
âï¸ ä¸‹ä¸€ç«™ï¼šèœå•组件
åœ¨ä¸‹ä¸€ç¯‡æ–‡ç« ä¸ï¼Œæˆ‘们将å¦ä¹ 如何创建一个èœå•ç»„ä»¶ï¼Œå¹¶è®©æˆ‘ä»¬çš„åº”ç”¨ç¨‹åºæ›´åŠ ç”ŸåŠ¨æœ‰è¶£ï¼æ•¬è¯·æœŸå¾…ï¼