Caleb (Livewire 作者)解决 Livewire 生态系统中最紧迫的问题之一:碎片化。由于有三种不同的创建 Livewire 组件的方式(传统方式、Volt 函数式、Volt 类式),社区已经分裂,新手对于"正确"构建组件的方式感到困惑。
"整个社区都分叉了,"Porzio 承认。"有三种不同的方式来制作 livewire 组件。这对新手不好,对老手也不好,每个人都在猜测什么是最好的方式。"
他的解决方案?第四种方式来统一一切。
在 Livewire 4 中,运行 php artisan make:livewire counter
默认会创建一个单文件、基于类的组件。这不再是 Volt——这就是 Livewire,由自定义解析器提供支持。
<?php
use Livewire\Component;
new class extends Component {
public $count = 1;
public function increment()
{
$this->count++;
}
public function decrement()
{
$this->count--;
}
}; ?>
<div>
<h1>{{ $count }}</h1>
<button wire:click="increment">+</button>
<button wire:click="decrement">-</button>
</div>
<script>
this.watch('count', (value) => {
console.log('Count changed to', value);
});
</script>
不再需要 @script
指令。只需在文件底部添加 <script>
标签,并使用 this.
而不是 $wire
:
<script>
this.watch('count', (value) => {
console.log('Count changed to', value)
})
</script>
可以钩入请求生命周期的任何部分:
this.intercept('increment', ({ proceed, cancel }) => {
if (confirm('Are you sure?')) {
proceed()
} else {
cancel()
}
})
不是每个人都喜欢单文件组件,Porzio 理解这一点。当你在现有组件上运行 make:livewire
时,你会得到这个选项:
php artisan make:livewire counter --mfc
这会创建一个多文件组件(MFC)结构:
⚡ counter/
├── counter.php
├── counter.blade.php
└── counter.js
PHP 文件是纯 PHP(匿名类),Blade 文件是纯 Blade,JavaScript 文件作为 ES6 模块提供,具有自动代码分割和缓存功能。
Livewire 4 引入了有主见的文件夹结构,以减少决策疲劳:
resources/views/components/
- 所有你的组件(Blade 和 Livewire)resources/views/pages/
- 页面级组件resources/views/layouts/
- 布局组件这些目录会自动创建命名空间,使组件组织更清洁、更可预测。
这里变得有趣(也有争议)。Porzio 引入了闪电符号表情符号(⚡)作为 Livewire 组件的文件前缀:
⚡counter.livewire.php
虽然承认他可能是唯一喜欢它的人,但 Porzio 认为 Unicode 表情符号在所有文件系统中都有效,并提供即时的视觉识别。在其他框架(SvelteKit、Next.js)甚至编程语言(如 Mojo)中都有先例。
"我们什么时候不再开心了?"Porzio 问道。"反正我们都会失业的...让我们在路上用表情符号开心一下。"
Livewire 4 中最令人兴奋的特性之一是与 PHP 8.4 新的属性钩子深度集成:
public int $count {
set => max(1, $value);
}
这会自动阻止计数低于 1,在许多情况下消除了更新钩子的需要。
public int $multiple {
get => $this->count * 5;
}
在模板中像访问任何其他属性一样访问它:
<div>Count: {{ $count }}, Times 5: {{ $multiple }}</div>
你甚至可以直接在属性钩子中实现缓存:
public string $expensiveData {
get => cache()->remember("data-{$this->id}", 3600, fn() => $this->fetchExpensiveData());
set => cache()->put("data-{$this->id}", $value, 3600);
}
PHP 8.4 还支持不对称属性可见性:
public private(set) string $readOnlyProperty;
这用原生 PHP 功能替换了 Livewire 的 #[Locked]
属性。
Livewire 4 会自动为任何触发网络请求的元素添加 data-loading
属性:
<button wire:click="save" class="data-[loading]:opacity-50">Save</button>
<div class="data-[loading]:block hidden">
<span>Loading...</span>
</div>
这与 Tailwind CSS 4 的数据属性选择器完美集成。
经过多年的请求,Livewire 4 终于支持插槽:
<livewire:modal>
<form wire:submit="save">
<input wire:model="title" />
<button type="submit">Save</button>
</form>
</livewire:modal>
模态组件的工作方式与 Blade 组件完全相同:
<!-- modal.blade.php -->
<div class="modal">{{ $slot }}</div>
伴随插槽而来的是新的 wire:ref
系统,用于父子通信:
<livewire:modal wire:ref="modal">
<form wire:submit="save">
<!-- form content -->
</form>
</livewire:modal>
public function save()
{
// Save logic...
$this->dispatch('close')->to(ref: 'modal');
}
当你需要直接的父子通信时,这消除了复杂事件广播模式的需要。
到目前为止,我们介绍了新的渲染引擎和拉取功能。接下来,让我们深入了解性能优化。
在深入解决方案之前,Caleb 展示了许多 Laravel 开发者面临但很少测量的关键性能问题。使用简单的基准测试,他展示了 Blade 组件如何成瓶颈的
// 简单基准测试:循环中的 25,000 个 Blade 组件
$start = microtime(true);
for ($i = 0; $i < 25000; $i++) {
// 渲染一个简单的 Blade 组件
}
$end = microtime(true);
echo ($end - $start) * 1000 . " milliseconds";
令人震惊的结果:
这揭示了即使是缓存的 Blade 组件也有显著的开销——比普通 PHP 慢约 10 倍。
Blaze 是一个革命性的 Blade 优化层,使用代码折叠来消除运行时开销。Blaze 不是积极缓存(这会产生缓存管理问题),而是在编译时分析你的 Blade 模板并预渲染静态部分。
// Blaze 之前 - 具有框架开销的复杂编译输出
<?php $__env->startComponent('components.card'); ?>
<?php $__env->slot('title'); ?>Product Name<?php $__env->endSlot(); ?>
<div class="content"><?php echo e($product->description); ?></div>
<?php echo $__env->renderComponent(); ?>
// Blaze 之后 - 清洁、优化的输出
<div class="card">
<h3 class="card-title">Product Name</h3>
<div class="content"><?php echo e($product->description); ?></div>
</div>
代码折叠识别模板中在生产环境中永远不会改变的部分:
{{-- 这个 Blade 组件 --}}
<x-card class="bg-white shadow-lg">
<x-slot:title>{{ $title }}</x-slot:title>
<div class="p-4">{{ $content }}</div>
</x-card>
Blaze 识别出卡片结构、CSS 类和 HTML 元素是静态的。只有 $title
和 $content
变量是动态的。它在编译时预渲染静态部分。
composer require livewire/blaze
就是这样!Blaze 与你现有的 Blade 模板透明地工作。你可以选择使用配置进行配置,但目标是零配置优化。
演示中的实际结果:
这在保持完全相同的开发者体验的同时,性能提升了 10 倍以上。
传统的 Livewire 组件在任何更新时都会完全重新渲染。考虑这个仪表板:
class Dashboard extends Component
{
public function render()
{
return view('dashboard', [
'analytics' => $this->getAnalytics(), // 快速查询
'revenue' => $this->getAccountRevenue(), // 慢 - 1 秒查询
'reports' => $this->getReports(), // 快速查询
]);
}
public function generateReport()
{
// 这个动作强制重新渲染所有内容
// 包括慢速的收入计算
}
}
每个动作都会触发昂贵的 getAccountRevenue()
查询,使整个界面变得迟缓。
Islands 允许你隔离模板中昂贵的部分:
<div class="dashboard">
{{-- 快速部分 - 始终响应 --}}
<div class="analytics">
@foreach($analytics as $metric)
<x-metric :value="$metric->value" :label="$metric->name" />
@endforeach
</div>
{{-- 慢速部分 - 在岛上隔离 --}} @island
<div class="revenue-chart">
<x-chart :data="$this->accountRevenue" />
</div>
@endisland {{-- 快速部分 - 始终响应 --}}
<div class="reports">
<button wire:click="generateReport">Generate Report</button>
<button wire:click="downloadReport">Download</button>
</div>
</div>
Islands 之前:
Islands 之后:
Islands 支持带有优雅占位符处理的懒加载:
@island(lazy: true) @placeholder
<x-revenue-placeholder />
@endplaceholder
<div class="revenue-section">
<x-chart :data="$this->expensiveRevenueData" />
</div>
@endisland
用户体验:
@island('reports') @foreach($reports as $report)
<div class="report-item">{{ $report->title }}</div>
@endforeach @endisland {{-- 这个按钮在岛外但目标是它 --}}
<button wire:island="reports" wire:click="loadMoreReports">Load More</button>
{{-- 默认:替换岛内容 --}} @island('reports')
<!-- content -->
@endisland {{-- 追加模式:将新内容添加到现有内容 --}}
<button wire:island="reports" wire:click="loadMore" wire:render="append">Load More</button>
{{-- 前置模式:将内容添加到开头 --}}
<button wire:island="chat" wire:click="loadOlderMessages" wire:render="prepend">Load Older Messages</button>
终极现代 UX 模式:
@island('posts', render: 'append') @foreach($posts as $post)
<article class="post">{{ $post->content }}</article>
@endforeach @endisland {{-- 不可见触发元素 --}}
<div wire:island="posts" wire:intersect="$paginator.nextPage()" class="h-4"></div>
这创建了真正的无限滚动:
特定部分的实时更新:
@island(poll: '5s')
<div class="live-metrics">
<span>Active Users: {{ $activeUsers }}</span>
<span>Revenue Today: ${{ number_format($todayRevenue) }}</span>
</div>
@endisland
只有岛进行轮询——你页面的其余部分保持不变。
结合所有三个优化(新渲染引擎 + Blaze + Islands):
{{-- 一个复杂的仪表板,立即加载并保持响应 --}}
<div class="dashboard">
{{-- 使用 Blaze 优化的快速部分 --}}
<x-metric-grid :metrics="$quickMetrics" />
{{-- 昂贵的部分隔离和懒加载 --}} @island('revenue', lazy: true) @placeholder
<x-revenue-skeleton />
@endplaceholder
<x-revenue-chart :data="$expensiveRevenueData" />
@endisland {{-- 带无限滚动的交互部分 --}} @island('reports', render: 'append') @foreach($reports as $report)
<x-report-card :report="$report" />
@endforeach @endisland
<div wire:island="reports" wire:intersect="$paginator.nextPage()"></div>
</div>
性能特征:
Livewire v4 的性能功能遵循一个关键原则:以最小的复杂性获得最大的性能。
{{-- 这就是无限滚动所需的全部 --}} @island('posts', render: 'append') @foreach($posts as $post)
<x-post :post="$post" />
@endforeach @endisland
<div wire:island="posts" wire:intersect="$paginator.nextPage()"></div>
与需要以下内容的传统 JavaScript 方法相比:
Islands 的美妙之处在于它们的可组合性。学习基本原语:
@island
<!-- expensive content -->
@endisland
然后与修饰符结合:
lazy: true
- 懒加载poll: '5s'
- 实时更新render: 'append'
- 追加渲染wire:intersect
- 交集触发器Livewire v4 代表了 Laravel 应用程序的巨大飞跃。对于 Laravel 开发者来说,这意味着你可以构建复杂的、交互式的应用程序,而不牺牲性能或开发者体验。在"快速"和"功能丰富"之间选择的时代已经结束。