能否在Symfony UX Twig组件中创建动态block?
Symfony UX Twig组件:动态标签面板内容实现方案
背景与问题
我基于Symfony UX Twig组件开发标签页组件,目标是替代Ember、React等前端组件的类似功能。当前编写的标签页Twig组件代码如下:
{% set tabID = "tabs_" ~ random() %} <div> <div class="sm:hidden"> <label for="tabs" class="sr-only"> Select a tab </label> <select id="{{tabID}}" name="{{tabID}}" value="{% for tab in tabs|filter(a => a.current == true) %} {{tab.name}}{% endfor %}" class="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm" > {% for tab in tabs %} <option {% if tab.current %} selected {% endif %} key={{tab.name}}>{{tab.name}}</option> {% endfor %} </select> </div> <div class="hidden sm:block"> <div class="border-b border-gray-200"> <nav aria-label="Tabs" class="-mb-px flex space-x-8" role="tablist"> {% for tab in tabs %} <button aria-selected='{{ tab.current ? "true" : "false" }}' aria-controls='{{tab.name}}' role='tab' class='whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium {{ tab.current ? "border-indigo-500 text-indigo-600" : "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" }}' > {{tab.name}} </button> {% endfor %} </nav> </div> </div> <div class='panel-content'> {% for tab in tabs %} {% set nameBlock = 'panel_' ~ loop.index %} <div class="panel {% if not tab.current %} hidden {% endif %}"> {% block nameBlock %} {% endblock %} </div> {% endfor %} </div> </div>
由于Twig不支持动态命名的block,我希望实现如下调用方式,为每个标签面板传递自定义内容:
<twig:Tabs tabs="{{tabs}}"> <twig:block name="panel_0"> GROSS 1 </twig:block> <twig:block name="panel_1"> GROSS 2 </twig:block> <twig:block name="panel_2"> GROSS 3 </twig:block> <twig:block name="panel_3"> GROSS 4 </twig:block> </twig:Tabs>
请问该如何实现此类功能?是否需要借助PHP组件文件并使用#[PostMount]注解方法?或是只能通过手动创建名为panel的子组件来实现?
可行实现方案
方案一:PHP组件类 + #[PostMount]注解
通过创建对应的PHP组件类,捕获并处理组件内部的子块内容,将其与标签项关联:
- 创建PHP组件类
// src/Twig/Component/TabsComponent.php namespace App\Twig\Component; use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; use Symfony\UX\TwigComponent\Attribute\PostMount; use Symfony\UX\TwigComponent\MountedComponent; #[AsTwigComponent('Tabs')] class TabsComponent { public array $tabs = []; public array $panels = []; #[PostMount] public function postMount(MountedComponent $mounted): void { // 遍历所有子块,收集面板内容 foreach ($mounted->getChildren() as $child) { if ($child->getName() === 'block' && isset($child->getAttributes()['name'])) { $panelName = $child->getAttributes()['name']; // 渲染子块内容并存储 $this->panels[$panelName] = $child->render(); } } } }
- 修改Twig组件模板
将原来的动态block替换为渲染存储的面板内容:
{% set tabID = "tabs_" ~ random() %} <div> <!-- 保留原有的标签选择器、导航按钮代码 --> <div class="sm:hidden"> <!-- ... 原代码不变 ... --> </div> <div class="hidden sm:block"> <!-- ... 原代码不变 ... --> </div> <div class='panel-content'> {% for tab in tabs %} {% set panelKey = 'panel_' ~ loop.index0 %} {# 用loop.index0匹配调用时的panel_0索引 #} <div class="panel {% if not tab.current %} hidden {% endif %}"> {{ panels[panelKey] ?? '' }} </div> {% endfor %} </div> </div>
这样就能按照你期望的方式调用组件,子块内容会被正确渲染到对应面板中。
方案二:使用TabPanel子组件
如果不想编写PHP代码,可以创建一个TabPanel子组件,通过嵌套传递面板内容:
- 创建TabPanel子组件模板
{# templates/components/TabPanel.html.twig #} {% block content %}{% endblock %}
- 修改Tabs组件模板
在模板中遍历子组件,匹配对应的标签索引:
{% set tabID = "tabs_" ~ random() %} <div> <!-- 保留原有的标签选择器、导航按钮代码 --> <div class="sm:hidden"> <!-- ... 原代码不变 ... --> </div> <div class="hidden sm:block"> <!-- ... 原代码不变 ... --> </div> <div class='panel-content'> {% for tab in tabs %} {% set panelIndex = loop.index0 %} <div class="panel {% if not tab.current %} hidden {% endif %}"> {% for child in children %} {% if child.name == 'TabPanel' and child.attributes.index == panelIndex %} {{ child.render() }} {% endif %} {% endfor %} </div> {% endfor %} </div> </div>
- 调用方式调整
<twig:Tabs tabs="{{tabs}}"> <twig:TabPanel index="0"> GROSS 1 </twig:TabPanel> <twig:TabPanel index="1"> GROSS 2 </twig:TabPanel> <twig:TabPanel index="2"> GROSS 3 </twig:TabPanel> <twig:TabPanel index="3"> GROSS 4 </twig:TabPanel> </twig:Tabs>
方案对比
- 方案一更贴近你最初期望的调用语法,但需要编写PHP组件类,适合需要高度自定义的场景。
- 方案二更简洁,无需PHP代码,通过子组件传递内容,更符合Twig组件的原生使用习惯。
内容的提问来源于stack exchange,提问作者UXerUIer
相关产品推荐
相关产品推荐

