循环调用Blazor模板函数包裹HTML元素时td未嵌套tr渲染异常问题
问题现象
开发带表格功能的Blazor组件时,抽离复用Razor模板片段出现渲染异常:生成的td元素未嵌套在tr元素内部,与<tr>处于同一DOM层级。其中tfoot部分渲染正常,tbody部分存在上述问题,无需通过返回MarkupString拼接原始HTML的方式即可解决。
复现代码如下:
<table> <tbody> @foreach (var row in data) { @:<tr> RenderRow(row); @:</tr> } </tbody> <tfoot> <tr> @if (footerRow != null) { RenderRow(footerRow); } </tr> </tfoot> </table> @{ void RenderRow(Row row) { <td>@row.RowNum</td> RenderRowHalf(row.Left); RenderRowHalf(row.Right); } void RenderRowHalf(RowHalf half) { <td>@half.Foo</td> <td>@(Util.ColorNumber(half.Bar))</td> } }
问题原因
Blazor的Razor引擎不是通过字符串拼接生成HTML,而是通过构建渲染树(RenderTree)映射DOM结构,要求HTML标签必须以完整的成对结构出现在Razor标记中,才能被正确识别为元素节点、建立父子层级关系:
tbody循环中使用@:语法单独输出<tr>开标签和</tr>闭标签时,引擎会将这两段内容识别为独立的平级内容块,不会将两者中间调用RenderRow生成的td节点归为tr的子节点,最终渲染出平级结构。tfoot部分的<tr>标签是完整成对写在Razor标记中的,引擎可以正确识别这是一个父元素节点,内部逻辑输出的td会自动挂载为其子节点,因此渲染正常。
正确实现方案
方案1:最小改动修正标签写法
不要拆分tr的开/闭标签用@:单独输出,直接将完整的tr标签写在循环块内,把渲染方法调用放在标签内部,和tfoot的写法保持一致即可:
<tbody> @foreach (var row in data) { <tr> @{ RenderRow(row); } </tr> } </tbody>
调用返回void的渲染方法时,放在@{}代码块中,或在方法名前加@调用均可正常渲染。
方案2:使用RenderFragment实现复用(官方推荐)
对于可复用的模板片段,更符合Blazor设计规范的写法是定义参数化RenderFragment委托,相比void渲染方法,渲染层级更可控,不会出现上下文错乱问题:
<table> <tbody> @foreach (var row in data) { <tr> @RenderRow(row) </tr> } </tbody> <tfoot> <tr> @if (footerRow != null) { @RenderRow(footerRow) } </tr> </tfoot> </table> @{ RenderFragment<Row> RenderRow = row => __builder => { <td>@row.RowNum</td> @RenderRowHalf(row.Left) @RenderRowHalf(row.Right) }; RenderFragment<RowHalf> RenderRowHalf = half => __builder => { <td>@half.Foo</td> <td>@(Util.ColorNumber(half.Bar))</td> }; }
注意:Blazor Razor语法和传统ASP.NET MVC的字符串拼接式Razor逻辑有本质区别,永远不要将一个HTML元素的开标签、闭标签拆分到不同的输出块中,否则必然出现渲染层级错乱。
内容的提问来源于stack exchange,提问作者Austin Wagner
相关产品推荐
相关产品推荐

