在上一个教程中,我们执行了一个“ Hello World”应用程序,并且仅处理了一些用户输入(“按CTRL+C到退出”)。
但是,我们并没有真正感觉到实际使用用户输入来更改模型数据的感觉,进而更改我们在应用程序中看到的内容。因此,在本教程中,我们将创建一个菜单组件,使我们可以在按钮之间移动。
ð定义我们的数据
对于任何气泡茶组件,我们需要的第一件事是我们的模型负责的数据。如果您记得在我们的简单页面模型中,数据只是我们显示的文本:
type simplePage struct { text string }
在我们的菜单中,我们需要做的是:
- 显示我们的选项
- 显示选择哪个选项
- 此外,让用户按ENTER转到另一个页面。但是我们将在以后的教程中添加。
- 目前,我们仍然可以通过使用OnPress函数来告诉我们,如果用户按Enter。
因此,我们的模型的数据看起来像这样;如果您正在关注,请将其写在名为menu.go
的文件中。
type menu struct {
options []menuItem
selectedIndex int
}
type menuItem struct {
text string
onPress func() tea.Msg
}
菜单由menuitems组成,每个menuitem都有文本和一个函数处理按Enter。在本教程中,我们将在全帽和全羊皮之间进行应用程序切换,因此至少在做某事。
它返回tea.Msg
,因为我们能够根据本用户输入更改数据。我们将在下一节中了解为什么在实现Model
接口时。
ð§实施模型接口
如果您回想起,让我们将模型用作UI组件,则需要实现此接口:
type Model interface {
Init() Cmd
Update(msg Msg) (Model, Cmd)
View() string
}
首先让我们写下初始功能。
func (m menu) Init() tea.Cmd { return nil }
再次,我们仍然没有任何初始Cmd
我们需要运行,因此我们可以返回nil
。
对于View
函数,让我们用箭头做一个老式菜单,以告诉我们当前选择了哪个项目。
func (m menu) View() string {
var options []string
for i, o := range m.options {
if i == m.selectedIndex {
options = append(options, fmt.Sprintf("-> %s", o.text))
} else {
options = append(options, fmt.Sprintf(" %s", o.text))
}
}
return fmt.Sprintf(`%s
Press enter/return to select a list item, arrow keys to move, or Ctrl+C to exit.`,
strings.Join(options, "\n"))
}
正如上一个教程中提到的那样,使泡泡茶真正可以学习的一件事是,UI的显示基本上是一个大字符串。因此,在menu.View
中,我们制作了一片字符串,其中所选选项具有箭头,而未选择的选项具有领先空间。然后,我们将它们一起加入在一起,并将我们的污垢添加到底部。
最后,让我们编写我们的更新方法来处理用户输入。
func (m menu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
case tea.KeyMsg:
switch msg.(tea.KeyMsg).String() {
case "ctrl+c":
return m, tea.Quit
case "down", "right", "up", "left":
return m.moveCursor(msg.(tea.KeyMsg)), nil
}
}
return m, nil
}
func (m menu) moveCursor(msg tea.KeyMsg) menu {
switch msg.String() {
case "up", "left":
m.selectedIndex--
case "down", "right":
m.selectedIndex++
default:
// do nothing
}
optCount := len(m.options)
m.selectedIndex = (m.selectedIndex + optCount) % optCount
return m
}
Update
方法是该应用程序中最复杂的部分,所以让我们分解。
case "ctrl+c":
return m, tea.Quit
像以前一样,我们正在处理KeyMsg
类型,而我们是CTRL+C按键,通过退回cmd来戒烟。
case "down", "right", "up", "left":
return m.moveCursor(msg.(tea.KeyMsg)), nil
对于箭头键,我们使用辅助函数moveCursor
,该功能返回更新的模型。
func (m menu) moveCursor(msg tea.KeyMsg) menu {
switch msg.String() {
case "up", "left":
m.selectedIndex--
case "down", "right":
m.selectedIndex++
default:
// do nothing
}
optCount := len(m.options)
m.selectedIndex = (m.selectedIndex + optCount) % optCount
return m
}
向上和左keymsg字符串用作我们的“导航”键,向下和右侧的键将我们导航,减少和递增m.selected
。
然后,我们使用mod运算符来确保m.selected
是我们选项的指标之一。
最后,随着模型的更新,moveCursor
返回了Update
返回的模型,而新模型最终通过我们的View
方法处理。
不过,在我们继续处理Enter密钥之前,我们应该看到应用程序运行。因此,让我们将新的menu
组件放入main
函数并运行它。
func main() {
m := menu{
options: []menuItem{
menuItem{
text: "new check-in",
onPress: func() tea.Msg { return struct{}{} },
},
menuItem{
text: "view check-ins",
onPress: func() tea.Msg { return struct{}{} },
},
},
}
p := tea.NewProgram(m)
if err := p.Start(); err != nil {
panic(err)
}
}
目前,OnPress只是一个返回空结构的无开机。现在,让我们运行我们的应用程序。
go build
./check-ins
您应该看到这样的东西:
酷!现在,菜单可以切换选择的内容!现在让我们处理该用户输入。
处理Enter键并查看CMD类型的实际功能
到目前为止,我们还没有真正仔细研究tea.Cmd
类型。这是Update
方法的两个返回值之一,但是我们只使用它来退出应用程序。让我们仔细看看它的类型签名。
type Cmd func() tea.Msg
a Cmd
是某种功能,可以做一些事情,然后给我们回到tea.Msg
。该功能可能是时间传递,可以是I/O喜欢检索一些数据,实际上一切都会发生! tea.Msg
反过
因此,处理用户按Enter键,然后运行任意的ONPRESS函数,是使用CMD的一种方式。因此,让我们从Enter Button处理程序开始。
case tea.KeyMsg:
switch msg.(tea.KeyMsg).String() {
case "q":
return m, tea.Quit
case "down", "right", "up", "left":
return m.moveCursor(msg.(tea.KeyMsg)), nil
+ case "enter", "return":
+ return m, m.options[m.selectedIndex].onPress
}
请注意,当用户按Enter按ENTER时,我们将返回模型,但我们也还返回所选项目的onPress
函数。如果您还记得我们定义menuItem
类型时,其onPress
字段的类型为func() tea.Msg
。换句话说,与Cmd
类型别名!
不过,在Update
方法中我们还需要做另一件事。现在,我们只处理tea.KeyMsg
类型。我们要返回的用于切换所选物品的大写字母的类型将是一种全新的ot tea.Msg
,因此我们需要定义它,然后在我们的更新方法中添加一个案例。首先,让我们定义结构。
type toggleCasingMsg struct{}
我们不需要传递任何数据,因此我们的MSG只是一个空结构。如果您还记得的话,tea.Msg
类型只是一个空接口,因此我们可以在需要的数据中包含一个或很少的数据。
现在回到更新方法中,让我们添加一个toggleCasingMsg
!
首先添加方法toggleSelectedItemCase
func (m menu) toggleSelectedItemCase() tea.Model {
selectedText := m.options[m.selectedIndex].text
if selectedText == strings.ToUpper(selectedText) {
m.options[m.selectedIndex].text = strings.ToLower(selectedText)
} else {
m.options[m.selectedIndex].text = strings.ToUpper(selectedText)
}
return m
}
然后将其添加到Update
方法中。
func (m menu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
+ case toggleCasingMsg:
+ return m.toggleSelectedItemCase(), nil
case tea.KeyMsg:
// our KeyMsg handlers here
在togglecasingmsg上,我们更新了所选菜单项的外壳,然后返回更新的模型。
最后,在app.go中,让我们使用我们的togglecasingmsg
menuItem{
text: "new check-in",
- onPress: func() tea.Msg { return struct{}{} },
+ onPress: func() tea.Msg { return toggleCasingMsg{} },
},
menuItem{
text: "view check-ins",
- onPress: func() tea.Msg { return struct{}{} },
+ onPress: func() tea.Msg { return toggleCasingMsg{} },
},
现在让我们尝试我们的应用程序!
go build
./check-ins
该应用现在应该看起来像这样:
注意,顺便说一下,在应用程序的这个阶段,这不是我们可以处理的唯一方法;我们还可以完全处理更新功能中的所有切换,而不是必须使用CMD处理。我选择使用CMD的原因是:
- 在泡泡茶中显示一个简单的用例
- 通过使用CMD,我们可以将任意事件处理程序功能传递到我们的组件中,如果您在React中进行了编码,则可以将类似的模式传递到我们的组件中。
接下来,我们有一个菜单,但是还不是很浮华。在下一个教程中,我们将看到如何使用泡泡茶使我们的应用看起来很酷。首先,用泡泡茶的CSS般的唇彩包装!