百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Go 语言 Web 框架 Echo 系列:定制篇4—自定义 Renderer

bigegpt 2024-09-17 12:42 35 浏览

Render,顾名思义,要进行页面渲染。Go 语言不但自带有强大的 http 库,还自带了 HTML 模板引擎。Echo 框架对模板引擎进行了一些额外处理,并提供了给用户自定义页面渲染的接口。本文就相关问题进行探讨。

模板渲染

Echo 框架的 Context 接口提供了下面的方法进行页面渲染:

// echo 包中 Context 接口的方法
Render(code int, name string, data interface{}) error

其中,code 是 HTTP Status,name 是定义的模板名,data 是模板可能需要的数据。执行这个方法后,通过数据渲染模板,并发送带有 HTTP 状态的 text/html 响应。可以通过 Echo.Renderer 来注册模板,从而允许我们使用任何模板引擎。

Renderer 接口定义如下:

// Renderer is the interface that wraps the Render function.
type Renderer interface {
  Render(io.Writer, string, interface{}, Context) error
}

这里可能会有点迷糊,怎么有两个 Render 方法,而且它们的签名还不一样。这里的逻辑是这样的:

  • echo.Echo 类型有一个 Renderer 接口类型的字段,用来注册模板引擎;
  • echo.Context 接口类型有一个 Render 方法,在 Handle 中我们通过调用 Context 的 Render 方法进行模板渲染;
  • 在 Context 的 Render 方法内部(当然是 echo 中 Context 接口的默认实现),会调用 echo.Echo 的字段 Renderer 的 Render 方法,进行具体的模板渲染;

这里是具体的渲染源码:

func (c *context) Render(code int, name string, data interface{}) (err error) {
 if c.echo.Renderer == nil {
  return ErrRendererNotRegistered
 }
 buf := new(bytes.Buffer)
 if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
  return
 }
 return c.HTMLBlob(code, buf.Bytes())
}

可见,如果调用了 Context#Render 进行模板渲染,但并没有注册模板引擎则会报错(ErrRendererNotRegistered)。

集成标准库模板引擎

1、我们先定义一个类型:Template,然后实现 Echo.Renderer 接口,即提供 Render 方法。

type Template struct {
    templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return t.templates.ExecuteTemplate(w, name, data)
}

2、接着预编译一个模板。定义一个模板文件:template/index.html,内容如下:

{{define "index"}}Hello, {{.}}!{{end}}

然后预编译得到 Template 的实例:

tpl := &Template{
    templates: template.Must(template.ParseGlob("template/*.html")),
}

3、注册模板引擎:

e := echo.New()

e.Renderer = tpl

4、在 Handler 中渲染模板:

e.GET("/", func(ctx echo.Context) error {
  return ctx.Render(http.StatusOK, "index", "studygolang")
})

注意这里的 index 是模板文件中 define "index" ,而不是文件名。

编译后运行,浏览器正常显示:Hello,studygolang!

通用化定制

一般的,页面会有一些通用的部分,比如头部、尾部等。所以业界通常的做法是有一个 layout,而且还可能不止一个 layout,因为普通用户看到的和后台看到的头部、尾部一般会不一样。那这样的通用化定制需求该如何集成到 Echo 的 Render 中呢?

先考虑只有一种 layout 的情况。定义一个类型 layoutTemplate,实现 Echo.Renderer 接口:

type layoutTemplate struct{}

var LayoutTemplate = &layoutTemplate{}

func (l *layoutTemplate) Render(w io.Writer, contentTpl string, data interface{}, ctx echo.Context) error {
 layout := "layout.html"
 tpl, err := template.New(layout).ParseFiles("template/common/"+layout, "template/"+contentTpl)
 if err != nil {
  return err
 }

 return tpl.Execute(w, data)
}

然后注册该 Renderer,并在 Handler 中渲染,注意 ctx.Render 的第二个参数,跟上面说的不一样,我们传递的是子模板的文件名:index.html。

e := echo.New()

e.Renderer = render.LayoutTemplate

e.GET("/", func(ctx echo.Context) error {
  return ctx.Render(http.StatusOK, "index.html", nil)
})

这里用到了两个模板文件:layout.html 和 index.html,来源 Hugo 的 soho 这个模板。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Echo博客系统</title>

  <meta name="author" content="Go语言中文网站长polaris">

  <meta name="keywords" content="" />
  <meta name="description" content="" />

  <link type="text/css" rel="stylesheet" href="/static/css/print.css" media="print">
  <link type="text/css" rel="stylesheet" href="/static/css/poole.css"> 
  <link type="text/css" rel="stylesheet" href="/static/css/hyde.css">

  <link rel="stylesheet"
        href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700&display=swap">

  <link rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"
        integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk="
        crossorigin="anonymous" />

  <link rel="apple-touch-icon-precomposed"
        sizes="144x144"
        href="https://themes.gohugo.io//theme/soho/apple-touch-icon-144-precomposed.png">

  <link rel="shortcut icon" href="https://themes.gohugo.io//theme/soho/favicon.png">

  </head>

<body>
  <aside class="sidebar">
    <div class="container">
        <div class="sidebar-about">
            <div class="author-image">
                <img src="https://themes.gohugo.io/theme/soho/images/profile.png" class="img-circle img-headshot center" alt="Profile Picture">
            </div>
            <h1>Echo-Gopher</h1>
        </div>

        <nav>
            <ul class="sidebar-nav">
                <li> <a href="/">Home</a> </li>
                <li> <a href="/about/"> About </a> </li>
            </ul>
        </nav>

        <section class="social-icons">

            <a href="https://github.com/polaris1119" rel="me" title="GitHub">
                <i class="fab fa-github" aria-hidden="true"></i>
            </a>
            
            <a href="https://weibo.com/studygolang" rel="me" title="Weibo">
                <i class="fab fa-weibo" aria-hidden="true"></i>
            </a>
            
        </section>
    </div>
  </aside>

  <main class="content container">
    {{template "content" .}}
  </main>

  <footer>
    <div class="copyright">
      ? polaris 2020 · <a href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a>
    </div>
  </footer>

<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/js/all.min.js"
  integrity="sha256-MAgcygDRahs+F/Nk5Vz387whB4kSK9NXlDN3w58LLq0="
  crossorigin="anonymous"></script>
  
</body>
</html>

这是 layout.html 的内容,核心在于 {{template "content" .}},表示具体内容模板需要定义 content,所以看看 index.html 文件:

{{define "content"}}
<div class="posts">
    <article class="post">
        <h2 class="post-title">
            <a href="/">Echo 系列教程 — 定制篇3:自定义 Logger,用你喜欢的日志库</a>
        </h2>

        <div class="post-date">
            <time datetime="2020-03-06T00:00:00Z">Mar 06, 2020</time> · 3 min read
        </div>
        在知识星球简书项目中,我们分析对比了目前的一些日志库。虽然 Go 标准库有一个 log,但功能有限,所以才出现了很多第三方的日志库。
        <div class="read-more-link">
            <a href="http://blog.studygolang.com/2020/03/echo-custom-logger/">阅读全文</a>
        </div>
    </article>

    <article class="post">
        <h2 class="post-title">
            <a href="/">Echo 系列教程 — 定制篇2:自定义 Validator,进行输入校验</a>
        </h2>

        <div class="post-date">
            <time datetime="2020-02-28T00:00:00Z">Feb 28, 2020</time> · 4 min read
        </div>
        上一篇讲 Binder 时提到,参数自动绑定和校验是 Web 框架很重要的两个功能,可以极大的提升开发速度,并更好的保证数据的可靠性(服务端数据校验很重要)。
        <div class="read-more-link">
            <a href="http://blog.studygolang.com/2020/02/echo-custom-validator/">阅读全文</a>
        </div>
    </article>
</div>
{{end}}

运行后打开浏览器访问 http://localhost:2020 :

接下来看看如何处理多个 layout 的情况。

因为 Render 的签名是固定的,不同的 layout 通过什么方式告知 Render 呢?观察 Render 方法的参数:

Render(w io.Writer, name string, data interface{}, ctx echo.Context)

可以在 data 和 ctx 上下功夫:

  1. 将 data 指定为 map[string]interface{},layout 通过 data 传递;
  2. 通过 ctx 的 Set 方法设置 layout,方法内通过 ctx.Get 获取 layout;

先看第 1 种方式:

// NoNavRender 没有导航的 layout html 输出
func NoNavRender(ctx echo.Context, contentTpl string, data map[string]interface{}) error {
 if data == nil {
  data = make(map[string]interface{})
 }
 data["layout"] = "nonav_layout.html"

 return ctx.Render(http.StatusOK, contentTpl, data)
}

在 render 包中增加了一个 NoVaRender 函数,该函数要求 data 必须是 map[string]interface{},这样就可以做到将 layout 传递给 Render 方法,不过因为 Render 方法的 data 参数是 interface{} 类型,因此得做类型断言。

layout := "layout.html"

if data != nil {
  if dataMap, ok := data.(map[string]interface{}); ok {
    if layoutInter, ok := dataMap["layout"]; ok {
      layout = layoutInter.(string)
    }
  }
}

看看第 2 种方式如何实现:

// NoNavRender 没有导航的 layout html 输出
func NoNavRender(ctx echo.Context, contentTpl string, data interface{}) error {
 ctx.Set("layout", "nonav_layout.html")

 return ctx.Render(http.StatusOK, contentTpl, data)
}

在 Render 中获取 layout 的值:

layout := "layout.html"

layoutInter := ctx.Get("layout")
if layoutInter != nil {
  layout = layoutInter.(string)
}

两种方式个人觉得第 2 种更优雅。不过需要注意的是,两种方式要注意 layout 不能冲突,也就是不能他用。

另外,我个人建议,data 参数永远要么传递 nil,要么传递 map[string]interface{} 。个人感觉 Echo 的 Render 方法 data 参数的类型不应该用 interface{} 而是用 map[string]interface{},这样可以更方便地往 data 中加入更多全局的数据。在简书项目中,我们会通过其他方式弥补这个问题。

小结

通过本节,你应该掌握了 Render 的使用、集成和大项目 layout 的处理。

额外提一句,因为 Context.Render 方法最终是调用的 Context.HTML 方法进行渲染,因此我们也完全可以抛弃 Render 方法,而是使用自己的 Render。目前简书的代码(后续会改掉)和 studygolang 的源码采用的就是完全抛弃 Context.Render 的方式,主要考虑还是有一些 Render 不能很好满足的地方,比如上面说的多 layout、data 类型等,不过也是可以解决的。因此还是建议采用 Echo 框架的 Render。

本节完整代码点这里: https://github.com/polaris1119/go-echo-example/tree/0cd46e8b1f38317439e95d55e3fe29a173a2e3c1。


相关推荐

方差分析简介(方差分析通俗理解)

介绍方差分析(ANOVA,AnalysisofVariance)是一种广泛使用的统计方法,用于比较两个或多个组之间的均值。单因素方差分析是方差分析的一种变体,旨在检测三个或更多分类组的均值是否存在...

正如404页面所预示,猴子正成为断网元凶--吧嗒吧嗒真好吃

吧嗒吧嗒,绘图:MakiNaro你可以通过加热、冰冻、水淹、模塑、甚至压溃压力来使网络光缆硬化。但用猴子显然是不行的。光缆那新挤压成型的塑料外皮太尼玛诱人了,无法阻挡一场试吃盛宴的举行。印度政府正...

Python数据可视化:箱线图多种库画法

概念箱线图通过数据的四分位数来展示数据的分布情况。例如:数据的中心位置,数据间的离散程度,是否有异常值等。把数据从小到大进行排列并等分成四份,第一分位数(Q1),第二分位数(Q2)和第三分位数(Q3)...

多组独立(完全随机设计)样本秩和检验的SPSS操作教程及结果解读

作者/风仕在上一期,我们已经讲完了两组独立样本秩和检验的SPSS操作教程及结果解读,这期开始讲多组独立样本秩和检验,我们主要从多组独立样本秩和检验介绍、两组独立样本秩和检验使用条件及案例的SPSS操作...

方差分析 in R语言 and Excel(方差分析r语言例题)

今天来写一篇实际中比较实用的分析方法,方差分析。通过方差分析,我们可以确定组别之间的差异是否超出了由于随机因素引起的差异范围。方差分析分为单因素方差分析和多因素方差分析,这一篇先介绍一下单因素方差分析...

可视化:前端数据可视化插件大盘点 图表/图谱/地图/关系图

前端数据可视化插件大盘点图表/图谱/地图/关系图全有在大数据时代,很多时候我们需要在网页中显示数据统计报表,从而能很直观地了解数据的走向,开发人员很多时候需要使用图表来表现一些数据。随着Web技术的...

matplotlib 必知的 15 个图(matplotlib各种图)

施工专题,我已完成20篇,施工系列几乎覆盖Python完整技术栈,目标只总结实践中最实用的东西,直击问题本质,快速帮助读者们入门和进阶:1我的施工计划2数字专题3字符串专题4列表专题5流程控制专题6编...

R ggplot2常用图表绘制指南(ggplot2绘制折线图)

ggplot2是R语言中强大的数据可视化包,基于“图形语法”(GrammarofGraphics),通过分层方式构建图表。以下是常用图表命令的详细指南,涵盖基本语法、常见图表类型及示例,适合...

Python数据可视化:从Pandas基础到Seaborn高级应用

数据可视化是数据分析中不可或缺的一环,它能帮助我们直观理解数据模式和趋势。本文将全面介绍Python中最常用的三种可视化方法。Pandas内置绘图功能Pandas基于Matplotlib提供了简洁的绘...

Python 数据可视化常用命令备忘录

本文提供了一个全面的Python数据可视化备忘单,适用于探索性数据分析(EDA)。该备忘单涵盖了单变量分析、双变量分析、多变量分析、时间序列分析、文本数据分析、可视化定制以及保存与显示等内容。所...

统计图的种类(统计图的种类及特点图片)

统计图是利用几何图形或具体事物的形象和地图等形式来表现社会经济现象数量特征和数量关系的图形。以下是几种常见的统计图类型及其适用场景:1.条形图(BarChart)条形图是用矩形条的高度或长度来表示...

实测,大模型谁更懂数据可视化?(数据可视化和可视化分析的主要模型)

大家好,我是Ai学习的老章看论文时,经常看到漂亮的图表,很多不知道是用什么工具绘制的,或者很想复刻类似图表。实测,大模型LaTeX公式识别,出乎预料前文,我用Kimi、Qwen-3-235B...

通过AI提示词让Deepseek快速生成各种类型的图表制作

在数据分析和可视化领域,图表是传达信息的重要工具。然而,传统图表制作往往需要专业的软件和一定的技术知识。本文将介绍如何通过AI提示词,利用Deepseek快速生成各种类型的图表,包括柱状图、折线图、饼...

数据可视化:解析箱线图(box plot)

箱线图/盒须图(boxplot)是数据分布的图形表示,由五个摘要组成:最小值、第一四分位数(25th百分位数)、中位数、第三四分位数(75th百分位数)和最大值。箱子代表四分位距(IQR)。IQR是...

[seaborn] seaborn学习笔记1-箱形图Boxplot

1箱形图Boxplot(代码下载)Boxplot可能是最常见的图形类型之一。它能够很好表示数据中的分布规律。箱型图方框的末尾显示了上下四分位数。极线显示最高和最低值,不包括异常值。seaborn中...