Golang模板函数渲染其他模板时能否引用自身且规避初始化循环?
问题解答
完全可以实现可递归调用自身的模板渲染函数,你遇到的初始化循环报错是代码依赖顺序不合理导致的,并非Go模板的功能限制。
问题根源
你当前代码的初始化依赖形成了闭环:
- 全局变量
includeFuncs初始化时,内部的include匿名函数需要调用GetHTMLIncludeFuncs()获取函数表 - 而
GetHTMLIncludeFuncs()的返回值依赖已经初始化完成的includeFuncs
二者互相等待初始化完成,就触发了循环报错。
另外你原示例中还存在一个逻辑错误:Parse(templatePath)是把路径字符串当模板内容解析,不是读取路径对应的模板文件,需要先调用os.ReadFile读取文件内容再传入Parse方法。
修复方案(最小改动)
通过延迟绑定函数的方式打破循环,代码如下:
package main import ( "bytes" "errors" html_template "html/template" "os" ) // 先声明include函数类型的变量,暂不赋值 var includeFunc func(templatePath string, data interface{}) (string, error) var includeFuncs = html_template.FuncMap{ "include": func(templatePath string, data interface{}) (string, error) { // 调用后续赋值完成的includeFunc return includeFunc(templatePath, data) }, } func GetHTMLIncludeFuncs() html_template.FuncMap { return includeFuncs } // 把具体实现放在init函数中,此时includeFuncs已经初始化完成 func init() { includeFunc = func(templatePath string, data interface{}) (string, error) { templatePath = "templates/" + templatePath if _, err := os.Stat(templatePath); err != nil { return "", errors.New("Unable to find the template file " + templatePath) } content, err := os.ReadFile(templatePath) if err != nil { return "", err } var renderedTpl bytes.Buffer // 绑定已经初始化完成的函数表 tpl, err := html_template.New(templatePath).Funcs(GetHTMLIncludeFuncs()).Parse(string(content)) if err != nil { return "", err } if err := tpl.Execute(&renderedTpl, data); err != nil { return "", err } return renderedTpl.String(), nil } }
更推荐的生产级实现
你当前的实现每次调用include都会读磁盘、解析模板,性能极差,生产环境建议提前预加载所有模板到内存,用模板名关联,include函数直接从预加载的模板池取模板渲染即可,实现更简单也没有循环问题:
package main import ( "bytes" "html/template" "os" "path/filepath" ) var tplCache map[string]*template.Template func init() { tplCache = make(map[string]*template.Template) // 预加载templates目录下所有模板 tplRoot := "templates/" filepath.Walk(tplRoot, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return err } relPath, _ := filepath.Rel(tplRoot, path) content, _ := os.ReadFile(path) // 先创建模板绑定函数表,再解析内容 tpl := template.New(relPath).Funcs(GetHTMLIncludeFuncs()) tpl, _ = tpl.Parse(string(content)) tplCache[relPath] = tpl return nil }) } var includeFuncs = template.FuncMap{ "include": func(tplName string, data interface{}) (string, error) { tpl, ok := tplCache[tplName] if !ok { return "", errors.New("template not found: " + tplName) } buf := bytes.NewBuffer(nil) err := tpl.Execute(buf, data) return buf.String(), err }, } func GetHTMLIncludeFuncs() template.FuncMap { return includeFuncs }
内容的提问来源于stack exchange,提问作者Abbe
相关产品推荐
相关产品推荐

