Handlebars自定义eq助手在else语句中调用出现值未定义问题
问题重现
我用TypeScript实现了Handlebars的eq自定义助手,代码如下:
const handleOptions = ( handlebarsInstance: typeof handlebars, result: boolean | string, options: HandlebarsBlockOptions<typeof handlebars>, ): boolean | string => { if (options.fn && options.inverse) { // The helper is being used as a block helper return result ? options.fn(handlebarsInstance) : options.inverse(handlebarsInstance) } // The helper is being used in a sub-expression return result } function equality( this: typeof handlebars, arg1: unknown, arg2: unknown, options: HandlebarsBlockOptions<typeof handlebars>, ): boolean | string { logger.info('"equality" helpers triggered', { arg1, arg2, options, }) return handleOptions(this, arg1 == arg2, options) } export const registerEqualityHelper = ( handlebarsInstance: typeof handlebars, ) => { handlebarsInstance.registerHelper('eq', equality.bind(handlebarsInstance)) } // 注册助手 const handlebarInstance = handlebars.create() registerEqualityHelper(handlebarInstance)
该助手在多数场景正常,但使用如下模板时出现问题:
{{#eq var1 "foo"}}Text 1{{else eq var1 "bar"}}Text 2{{else}}Text 3{{/eq}}
当var1等于"bar"时,预期输出Text 2,实际输出Text 3。排查发现此时equality函数的arg1为undefined,导致undefined == "bar"判断为false,且仅在else分支调用助手时出现该问题。
尝试过用../var1或@root.var1指定变量,虽有效但不够友好;改用子表达式嵌套if的方式({{#if (eq var1 "foo")}}Text 1{{else if (eq var1 "bar")}}Text 2{{else}}Text 3{{/if}})能解决,但需要修改模板,不符合需求。
问题根源
- 错误绑定了助手的上下文:注册助手时用
equality.bind(handlebarsInstance)把this绑定成了Handlebars实例,而Handlebars块助手的this原本应该指向当前模板的数据上下文。 - 传递错误的上下文给分支函数:
handleOptions中调用options.fn和options.inverse时,传入的是Handlebars实例而非数据上下文,导致else分支里的变量无法从正确的上下文中获取,最终arg1变成undefined。
修复方案
不需要修改模板,只需调整助手的实现逻辑,确保正确传递数据上下文:
1. 修改handleOptions函数
将第一个参数改为数据上下文,调用分支函数时传入该上下文:
const handleOptions = ( context: unknown, result: boolean | string, options: HandlebarsBlockOptions<typeof handlebars>, ): boolean | string => { if (options.fn && options.inverse) { // 传递数据上下文给块分支 return result ? options.fn(context) : options.inverse(context) } // 子表达式场景直接返回结果 return result }
2. 调整equality函数的上下文类型
将this的类型改为数据上下文(unknown),并将其传递给handleOptions:
function equality( this: unknown, // 指向模板的数据上下文 arg1: unknown, arg2: unknown, options: HandlebarsBlockOptions<typeof handlebars>, ): boolean | string { logger.info('"equality" helpers triggered', { arg1, arg2, options, }) return handleOptions(this, arg1 == arg2, options) }
3. 修正助手注册方式
移除bind(handlebarsInstance),让Handlebars自动将数据上下文绑定到助手的this:
export const registerEqualityHelper = ( handlebarsInstance: typeof handlebars, ) => { handlebarsInstance.registerHelper('eq', equality) }
验证效果
修改后重新运行模板{{#eq var1 "foo"}}Text 1{{else eq var1 "bar"}}Text 2{{else}}Text 3{{/eq}},当var1为"bar"时,第一个eq判断为false进入else分支,此时分支内的eq助手能正确获取var1的值,判断"bar" == "bar"为true,输出预期的Text 2。
内容的提问来源于stack exchange,提问作者Guillaume C.

