You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在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组件类,捕获并处理组件内部的子块内容,将其与标签项关联:

  1. 创建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();
            }
        }
    }
}
  1. 修改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子组件,通过嵌套传递面板内容:

  1. 创建TabPanel子组件模板
{# templates/components/TabPanel.html.twig #}
{% block content %}{% endblock %}
  1. 修改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>
  1. 调用方式调整
<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 01:34:57