我如何在 Go 中构建 Web 前端

文摘   2024-11-09 16:00   河南  

点击上方蓝字,后台回复【合集】获取 Go资料 

介绍

Go 是一种用于构建各种后端服务(如 API 或微服务)的绝佳语言,许多人为此使用它。但是 Web 前端呢,特别是动态呈现的 Web 应用程序呢?我们来看一下…

为什么我会选择 Go?

对于我创建的每个副项目,几乎肯定需要一个 Web 前端。在过去的几年里,我尝试了许多不同的解决方案,这些解决方案似乎非常适合这项工作。特别是当涉及到使用 React、Vue、Gatsby、Next.js 等构建的客户端渲染页面时。但是每次我尝试使用这些框架构建 Web 应用程序时都感觉过于复杂,所以我最终将它们扔掉并退回去到使用 Go 在服务器端呈现的基本 HTML 页面。但我不想谈论我在这些方面遇到的困难,因为这是另一篇文章。

使用Go渲染HTML

如果您以前没有听说过它,Go 带有一个内置的 html 模板引擎,您可以在html/template包中找到它。语法起初并不那么直观,但实际上非常简单并且可以完美地完成工作。以下是如何解析和呈现 html 模板的快速示例:
import "html/template"

tmpl, _ := template.New("").Parse("<h1>{{.}}</h1>") // from string
tmpl, _ := template.ParseFiles("demo.html")         // from file

tmpl.Execute(w, "Hello world")
// Output: <h1>Hello world</h1>
我现在不想详细介绍模板语法是如何工作的,因为我更愿意关注我为自己开发的处理更复杂模板结构的模式。每个可能的模板指令的参考都可以在文本/模板文档中找到。

我如何构建我的模板?

当谈到 Go 时,如何处理模板片段(例如页眉、内容、页脚)有两种可能性。

1.WordPress的方式 2.Django、Rails、Laravel 的方式

WordPress 方式(不要这样做)

WordPress 构建模板的方式是有一个header.html和一个footer.html文件,以及其他几个用于中间内容的方法。这不是一个好主意,因为它会将 HTML 标签分成两半。一个例子是:
<!-- header.html -->
{{define "header"}}
<html>
    <body>
        <div class="navbar">...</div>
        <div class="content">
{{end}}
<!-- profile.html -->
{{template "header" .}}

<div class="profile">
    Your username: {{.User.Name}}
</div>

{{template "footer" .}}
<!-- footer.html -->
{{define "footer"}}
        </div>
    </body>
</html>
{{end}}
相应的 Go 代码如下所示:
tmpl, _ := template.ParseFiles("header.html""footer.html""profile.html")
tmpl.Execute(w, User{Name: "philippta"})

Django、Rails、Laravel 方式(这样做)

构建模板的更好方法是拥有父模板和子模板,就像在 Django、Rails 或 Laravel 中定义它们一样。这种方法会产生更干净、更易于维护的模板,因为它不会将 HTML 标记减半,而且您不必担心忘记关闭另一个文件中的 HTML 标记。一个例子是:
<!-- layout.html -->
<html>
    <body>
        <div class="navbar">...</div>
        <div class="content">
            {{block "content" .}}
                <!-- Fallback, if "content" is not defined elsewhere -->
            {{end}}
        </div>
    </body>
</html>
<!-- profile.html -->
{{define "content"}}
<div class="profile">
    Your username: {{.User.Name}}
</div>
{{end}}

相应的 Go 代码如下所示:

tmpl, _ := template.ParseFiles("layout.html""profile.html")
tmpl.Execute(w, User{Name: "philippta"})
使用这种方法,您可以随意添加任意数量的子模板,并且不能忘记包含页眉和页脚模板。您还可以选择定义多个“内容”块,如果您想包含其他样式表、脚本或仅更改标题,这将非常有用。这是另一个快速示例:
<!-- layout.html -->
<html>
    <head>
        <title>{{block "title"}}Default page title{{end}}</title>

        <script src="app.js"></script>
        {{block "additional_scripts"}}{{end}}
    </head>
    <body>
        <div class="navbar">...</div>
        <div class="content">
            {{block "content" .}}
                <!-- Fallback, if "content" is not defined elsewhere -->
            {{end}}
        </div>
    </body>
</html>

实现模板渲染器

我们现在已经介绍了如何构建模板的基础知识,让我来谈谈如何渲染它们。在前面的示例中,我选择直接内联编译和执行模板,但这不能很好地扩展并且会很快导致错误。特别是在处理数据或自定义模板函数时。所以为了避免我为每个模板创建特定的渲染函数。这背后没有太多科学,它只是为了让我的生活更轻松。

封装结构

但是在我详细说明之前,我想快速地向您介绍一个假设的Go目录结构的外观。有一个main.go文件包含所有处理程序的服务器代码,最终将模板呈现给浏览器。然后有一个html目录/包,其中包含所有 HTML 模板文件以及一个html.go包含所有模板相关功能的文件。我知道 Go 的标准库已经定义了一个html包,并且可能存在命名冲突,但是由于这些函数唯一使用的地方是我们的html包本身,所以它非常安全。Ben Johnson 的标准封装布局最好地描述了这种方法。
html/
    layout.html
    dashboard.html
    profile/
        show.html
        edit.html
    html.go         <-- here are the rendering functions
main.go             <-- here could be the http handlers
因此,让我们查看该html.go文件,看看那里定义了什么。

捆绑模板

我正在使用embedGo 1.16 中引入的包来将 HTML 模板捆绑到生成的 Go 二进制文件中。该//go:embed *指令将收集当前html目录中的所有文件,并使files变量中的内容可用以供以后解析。它还有一个额外的好处,您不必担心从正确的位置运行 Go 二进制文件,因为在使用.ParseFiles().
import "embed"

//go:embed *
var files embed.FS

解析模板

由于模板现在捆绑在一起并且通常在files变量中可用,我可以开始解析它们。为此,我总是创建一个小的辅助函数,它不导出,只在这个html包中使用。另请注意,我使用的是新方法.ParseFS()而不是.ParseFiles()现在。
import "html/template"

func parse(file string) *template.Template {
 return template.Must(
        template.New("layout.html").ParseFS(files, "layout.html", file))
}
这是可选的,但如果我需要额外的自定义模板函数,我还会添加另一个全局变量template.FuncMap并将其添加到解析代码中,如下所示:
var funcs := template.FuncMap{
    "uppercase": func(v string) string {
        return strings.ToUpper(v)
    },
}
func parse(file string) *template.Template {
     return template.Must(
-          template.New("layout.html").ParseFS(files, "layout.html", file))
+          template.New("layout.html").Funcs(funcs).ParseFS(files, "layout.html", file))
  }
有了这个辅助函数,我就有机会非常轻松地解析所有文件。像files,我也将它们分配给包全局变量。我知道全局变量不被认为是一种好方法,但由于它们非常基础且未导出,因此风险非常小。
var (
    dashboard   = parse("dashboard.html")
    profileShow = parse("profile/show.html")
    profileEdit = parse("profile/edit.html")
)

模板辅助结构和函数

下一部分是创建开发人员友好的渲染功能。这些非常容易,因为它们只需要一个io.Writer所需的模板参数并执行该模板。我对传递给执行函数的数据使用了定义良好的结构,而不是接口,因为它为模板变量提供了更好的类型安全性。有了这个,我只需要确保一次,模板中使用的所有变量都被正确填充。每当我的数据发生变化时,我都会在编译时获得有关该权利的反馈,而不是在浏览器中查看时发现缺少某些内容。
type DashboardParams struct {
    User       User
    Statistics []Statistics
}

func Dashboard(w io.Writer, p DashboardParams) error {
    return dashboard.Execute(w, p)
}

type ProfileShowParams struct {
    User        User
    ProfileInfo Profile
}

func ProfileShow(w io.Writer, p ProfileShowParams) error {
    return profileShow.Execute(w, p)
}

// and so on ...

使用新的模板函数

有了所有这些脚手架,现在以保存和开发人员友好的方式使用渲染这些 HTML 模板变得非常容易。只需设置您的 http 端点处理程序,填写模板参数结构并将模板执行到http.ResponseWriter.
import "github.com/philippta/myproject/html"

http.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
    user := getUser()
    stats := getStatistics()

    params := html.DashboardParams{
        User:       user,
        Statistics: stats,
    }
    html.Dashboard(w, params)
})

包起来

这基本上总结了我在 Go 中编写前端应用程序的方式。实施起来并不难,让我的生活更轻松。通过使用该html/template包而不是其他前端框架,您可以更加关注布局,而不会引入不必要的复杂性。

来源:https://philipptanlak.com/web-frontends-in-go/,侵删

Golang在发光
每天为大家分享最新的Golang相关的技术文章、干货资料、语言资讯、语言教程、实战项目等,供大家学习和提升!
 最新文章