mcpField
T
twig-component

twig-component

github.com/smnandre开发180111 次安装19 周前更新
代码仓库

关于 twig-component

面向 Symfony UX 的组件开发技能,使用 PHP 类和 Twig 模板构建可复用的服务端渲染 UI。涵盖带逻辑的类组件和轻量 Twig-only 组件,组件本身无需客户端 JavaScript。

主要功能

  1. 01PHP 类与 Twig 模板组件
  2. 02纯 Twig 匿名组件
  3. 03属性与派生值
  4. 04默认及命名内容块
  5. 05HTML 属性传递
  6. 06Symfony 服务依赖注入

适用场景

  1. 01构建可复用的按钮、提示框和卡片
  2. 02创建布局区块与导航项
  3. 03组合模态框结构和表单控件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/smnandre/symfony-ux-skills --skill twig-component

安装后在 Claude Code 中输入/twig-component确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api.md
gotchas.md
patterns.md

详细介绍

TwigComponent

通过 PHP 类 + Twig 模板构建可复用的 UI 组件。可以把它想成 React/Vue 组件,但由服务器渲染,且无需 JavaScript。

有两种类型:适用于需要逻辑、服务或计算属性的组件的类组件(PHP 类 + Twig 模板),以及适用于简单展示元素的匿名组件(仅 Twig,无需 PHP 类)。

何时使用 TwigComponent

当你需要带属性的可复用标记,且初始渲染后无需在服务器上重新渲染时,请使用 TwigComponent。如果组件需要响应用户输入(通过 AJAX 重新渲染、数据绑定、操作),请改用 LiveComponent。

适用场景:按钮、警告框、卡片、徽章、图标、表单控件、布局区块、导航项、表格行、模态框(仅结构)。

安装

composer require symfony/ux-twig-component

类组件

带有 #[AsTwigComponent] 注解的 PHP 类,并与 Twig 模板配套使用。

// src/Twig/Components/Alert.php
namespace App\Twig\Components;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

#[AsTwigComponent]
final class Alert
{
    public string $type = 'info';
    public string $message;
    public bool $dismissible = false;
}
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}" {{ attributes }}>
    {{ message }}
    {% if dismissible %}
        <button type="button" class="close">&times;</button>
    {% endif %}
</div>
{# Usage #}
<twig:Alert type="success" message="Saved!" />
<twig:Alert type="danger" message="Error occurred" dismissible />

{# With block content instead of message prop #}
<twig:Alert type="warning">
    <strong>Warning:</strong> Check your input
</twig:Alert>

匿名组件(仅 Twig)

无需 PHP 类。直接在模板中使用 {% props %} 声明属性。适用于没有逻辑的简单展示组件。

{# templates/components/Button.html.twig #}
{% props variant = 'primary', size = 'md', disabled = false %}

<button
    class="btn btn-{{ variant }} btn-{{ size }}"
    {{ disabled ? 'disabled' }}
    {{ attributes }}
>
    {% block content %}{% endblock %}
</button>
<twig:Button variant="danger" size="lg">Delete</twig:Button>

属性

公共属性(类组件)

公共属性会成为组件属性。必填属性没有默认值。

#[AsTwigComponent]
final class Card
{
    public string $title;           // Required
    public ?string $subtitle = null; // Optional
    public bool $shadow = true;      // Optional with default
}
使用 mount() 派生状态

使用 mount() 根据传入的属性计算值。该方法在组件初始化期间运行一次。

#[AsTwigComponent]
final class UserCard
{
    public User $user;
    public string $displayName;

    public function mount(User $user): void
    {
        $this->user = $user;
        $this->displayName = $user->getFullName();
    }
}
<twig:UserCard :user="currentUser" />
动态属性(冒号前缀)

在属性前加上 :,即可传入 Twig 表达式,而不是字符串字面值。

{# Pass a variable #}
<twig:Alert :type="alertType" :message="flashMessage" />

{# Pass an expression #}
<twig:UserList :users="users|filter(u => u.active)" />

块(插槽)

块允许父模板向组件的特定区域注入内容。

默认块

组件标签之间的内容会传入 {% block content %}:

{# Component template #}
<div class="card">{% block content %}{% endblock %}</div>

{# Usage #}
<twig:Card><p>This is the card content</p></twig:Card>
命名块
{# templates/components/Modal.html.twig #}
<dialog class="modal" {{ attributes }}>
    <header>{% block header %}Default Header{% endblock %}</header>
    <main>{% block content %}{% endblock %}</main>
    <footer>{% block footer %}{% endblock %}</footer>
</dialog>
<twig:Modal>
    <twig:block name="header"><h2>Confirm Action</h2></twig:block>
    <twig:block name="content"><p>Are you sure?</p></twig:block>
    <twig:block name="footer">
        <button>Cancel</button>
        <button>Confirm</button>
    </twig:block>
</twig:Modal>

计算属性

以 get 开头的方法可在模板中通过 this.xxx 访问。每次访问时都会重新计算(不会在重新渲染之间缓存——如需缓存,请参阅 LiveComponent 的 computed)。

#[AsTwigComponent]
final class ProductCard
{
    public Product $product;

    public function getFormattedPrice(): string
    {
        return number_format($this->product->getPrice(), 2) . ' EUR';
    }

    public function isOnSale(): bool
    {
        return $this->product->getDiscount() > 0;
    }
}
<div class="product">
    <span class="price">{{ this.formattedPrice }}</span>
    {% if this.onSale %}
        <span class="badge">Sale!</span>
    {% endif %}
</div>

属性

传递给组件的额外 HTML 属性可通过 {{ attributes }} 访问。这样,使用者就可以添加自定义类、id、数据属性等。

{# Usage #}
<twig:Alert type="info" message="Hello" class="my-class" id="main-alert" data-controller="alert" />

{# In component template -- renders class, id, data-controller #}
<div {{ attributes }}>...</div>
属性方法
{# Merge with defaults #}
<div {{ attributes.defaults({class: 'alert'}) }}>

{# Exclude specific #}
<div {{ attributes.without('id', 'class') }}>

{# Only render specific #}
<div id="{{ attributes.render('id') }}">

{# Check existence #}
{% if attributes.has('disabled') %}

将组件用作服务

组件是 Symfony 服务——自动装配可直接使用。依赖项使用构造函数,属性则使用公共属性。

#[AsTwigComponent]
final class FeaturedProducts
{
    public function __construct(
        private readonly ProductRepository $products,
    ) {}

    public function getProducts(): array
    {
        return $this->products->findFeatured(limit: 6);
    }
}
{# templates/components/FeaturedProducts.html.twig #}
<div class="featured-products">
    {% for product in this.products %}
        <twig:ProductCard :product="product" />
    {% endfor %}
</div>
{# Usage -- no props needed, data comes from service #}
<twig:FeaturedProducts />

生命周期钩子

use Symfony\UX\TwigComponent\Attribute\PreMount;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsTwigComponent]
final class DataTable
{
    public array $data;
    public string $sortBy = 'id';

    #[PreMount]
    public function preMount(array $data): array
    {
        // Modify/validate incoming data before property assignment
        $data['sortBy'] ??= 'id';
        return $data;
    }

    #[PostMount]
    public function postMount(): void
    {
        // Runs after all props are set
        $this->data = $this->sortData($this->data);
    }
}

嵌套组件

组件可以自然组合——像嵌套 HTML 元素一样嵌套它们:

<twig:Card>
    <twig:block name="header">
        <twig:Icon name="star" /> Featured
    </twig:block>
    <twig:block name="content">
        <twig:ProductList :products="featuredProducts">
            <twig:block name="empty">
                <twig:Alert type="info" message="No products found" />
            </twig:block>
        </twig:ProductList>
    </twig:block>
</twig:Card>

配置

# config/packages/twig_component.yaml
twig_component:
    anonymous_template_directory: 'components/'
    defaults:
        App\Twig\Components\: 'components/'

HTML 与 Twig 语法

{# HTML syntax (recommended -- better IDE support, more readable) #}
<twig:Alert type="success" message="Done!" />

{# Twig syntax (alternative -- useful in edge cases) #}
{% component 'Alert' with {type: 'success', message: 'Done!'} %}
{% endcomponent %}

所有情况下都优先使用 HTML 语法(<twig:...>)。Twig 语法({% component %})已过时,可读性也较差。

参考资料

另请参阅

  • UX Icons 可自然集成到 TwigComponent 模板中:在组件标记内使用 <twig:ux:icon name="lucide:check" />。
  • UX Map 可通过 {{ ux_map(map, {style: 'height: 400px;'}) }} 在 TwigComponent 模板中渲染。

评论

支持 Markdown