Revel模板中全局变量必须通过c.RenderArgs注入,不能直接访问或赋值,需用c.Render()传参或拦截器统一注入,且模板上下文不自动继承。

模板中访问全局变量必须通过 c.RenderArgs 注入
Revel 不自动提供全局变量(如 .AppName、.Version),所有模板可读数据都来自控制器调用 c.Render() 时写入的 c.RenderArgs。直接在模板里写 {{.AppName}} 会静默为空——框架不会报错,也不会 fallback 到配置或环境变量。
c.RenderArgs 是 map[string]interface{},但不能直接赋值
你不能写 c.RenderArgs["AppName"] = "MyApp",因为 RenderArgs 是只读映射(底层是 sync.Map 封装)。必须用 c.Render() 或显式设置函数传入:
- 推荐:在每个需要全局信息的 action 中,把公共字段作为命名参数传入,例如
c.Render("AppName", "MyApp", "Version", "1.2.0", "users", users) - 替代:在
app/init.go的init()函数中注册一个拦截器(revel.InterceptFunc),在BEFORE阶段统一注入:revel.InterceptFunc(func(c *revel.Controller, fc revel.FilterChain) revel.Result {<br> c.RenderArgs["AppName"] = revel.AppConfig.StringDefault("app.name", "Unknown")<br> c.RenderArgs["IsDev"] = revel.BUILD_MODE == "dev"<br> return fc.Next(c)<br>}, revel.BEFORE)
注意:拦截器方式需确保在 c.Render() 调用前执行,且不要覆盖控制器后续传入的同名键(后写入者胜出)。
避免在模板中重复判断或拼接全局值
全局信息常用于页脚版本号、CDN 域名、调试开关等。若在多个模板里都写 {{if eq .IsDev "true"}}<script src="/debug.js">{{end}}</script>,容易漏判或逻辑不一致。更稳妥的做法是:
- 在拦截器中预计算好布尔标志,比如
c.RenderArgs["DebugMode"] = revel.BUILD_MODE == "dev",模板中直接{{if .DebugMode}} - 用
set函数在主模板中定义一次,再通过{{template}}传递给子模板:{{set ."cdnHost" "https://cdn.example.com"}},然后{{template "header.html" .}} - 不要在子模板里重新查
.AppName—— 若父模板未传入,子模板上下文里就没有它(Revel 不继承根上下文)
自定义函数比全局变量更适合动态逻辑
像生成 URL、格式化时间、拼接路径这类操作,不适合塞进 RenderArgs,因为每次请求值都不同,且污染上下文。应该注册模板函数:
- 在
app/init.go添加:revel.TemplateFuncs["assetUrl"] = func(path string) string { return revel.AppConfig.StringDefault("cdn.host", "") + "/static/" + path } - 模板中直接用:
<script src="%7B%7BassetUrl%20" js></script> - 注意:函数注册后需重启应用才生效;函数内不可直接访问
c(无请求上下文),所有依赖必须提前注入或从参数传入
真正容易被忽略的是:全局变量和函数都**无法跨模板作用域自动共享**。比如你在 App/Index.html 里 set 了一个变量,app/views/layout.html 里是拿不到的——除非你显式把 . 或 $ 传进去。这点和很多其他框架不同,得时刻意识到 Revel 模板的上下文是严格按调用链传递的。


















