关于 twig-component
面向 Symfony UX 的组件开发技能,使用 PHP 类和 Twig 模板构建可复用的服务端渲染 UI。涵盖带逻辑的类组件和轻量 Twig-only 组件,组件本身无需客户端 JavaScript。
主要功能
- 01PHP 类与 Twig 模板组件
- 02纯 Twig 匿名组件
- 03属性与派生值
- 04默认及命名内容块
- 05HTML 属性传递
- 06Symfony 服务依赖注入
适用场景
- 01构建可复用的按钮、提示框和卡片
- 02创建布局区块与导航项
- 03组合模态框结构和表单控件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ smnandre/ symfony-ux-skills --skill twig-component安装后在 Claude Code 中输入/twig-component确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api.mdgotchas.mdpatterns.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">×</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 %})已过时,可读性也较差。
参考资料
- 完整 API(属性选项、钩子、配置、所有方法):references/api.md
- 模式(表单、表格、布局、组合、实际示例):references/patterns.md
- 注意事项(属性、命名、嵌套组件、常见问题):references/gotchas.md
另请参阅
- UX Icons 可自然集成到 TwigComponent 模板中:在组件标记内使用
<twig:ux:icon name="lucide:check" />。 - UX Map 可通过
{{ ux_map(map, {style: 'height: 400px;'}) }}在 TwigComponent 模板中渲染。