主题
Islands
Islands 允许你在 Livewire 组件内创建独立更新的隔离区域。当岛内发生操作时,只有该岛会重新渲染——而不是整个组件。
这为你提供了将组件分解为更小块的性能优势,而无需创建单独的子组件、管理 props 或处理组件通信的开销。
基本用法
要创建岛,请使用 @island 指令包装 Blade 模板的任何部分:
blade
<?php // resources/views/components/⚡dashboard.blade.php
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Revenue;
new class extends Component
{
#[Computed]
public function revenue()
{
// Expensive calculation or query...
return Revenue::yearToDate();
}
};
?>
<div>
@island
<div>
Revenue: {{ $this->revenue }}
<button type="button" wire:click="$refresh">Refresh</button>
</div>
@endisland
<div>
<!-- Other content... -->
</div>
</div>当点击"Refresh"按钮时,只有包含收入计算的岛会重新渲染。组件的其余部分保持不变。
因为昂贵的计算在计算属性内——它按需计算——它只会在岛重新渲染时被调用,而不会在页面的其他部分更新时被调用。但是,由于岛默认随页面加载,因此 revenue 属性仍将在初始页面加载期间计算。
延迟加载
有时你有昂贵的计算或缓慢的 API 调用,不应阻塞初始页面加载。你可以使用 lazy 参数将岛的初始渲染推迟到页面加载之后:
blade
<?php // resources/views/components/⚡dashboard.blade.php
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Revenue;
new class extends Component
{
#[Computed]
public function revenue()
{
// Expensive calculation or query...
return Revenue::yearToDate();
}
};
?>
<div>
@island(lazy: true)
<div>
Revenue: {{ $this->revenue }}
<button type="button" wire:click="$refresh">Refresh</button>
</div>
@endisland
<div>
<!-- Other content... -->
</div>
</div>岛最初将显示加载状态,然后在单独的请求中获取并渲染其内容。
Lazy 与 Deferred 加载
默认情况下,lazy 使用交叉观察器在岛在视口中变为可见时触发加载。如果你希望岛在页面加载后立即加载(无论可见性如何),请改用 defer:
blade
{{-- Loads when scrolled into view --}}
@island(lazy: true)
<!-- ... -->
@endisland
{{-- Loads immediately after page load --}}
@island(defer: true)
<!-- ... -->
@endisland自定义加载状态
你可以使用 @placeholder 指令自定义延迟岛加载时显示的内容:
blade
@island(lazy: true)
@placeholder
<!-- Loading indicator -->
<div class="animate-pulse">
<div class="h-32 bg-gray-200 rounded"></div>
</div>
@endplaceholder
<div>
Revenue: {{ $this->revenue }}
<button type="button" wire:click="$refresh">Refresh</button>
</div>
@endisland命名岛
要从组件中的其他地方触发岛,请为其命名并使用 wire:island 引用它:
blade
<div>
@island(name: 'revenue')
<div>
Revenue: {{ $this->revenue }}
</div>
@endisland
<button type="button" wire:click="$refresh" wire:island="revenue">
Refresh revenue
</button>
</div>wire:island 指令与 wire:click、wire:submit 等操作指令一起工作,以将它们的更新范围限定到特定的岛。
当你有多个同名的岛时,它们会链接在一起,并始终作为一组渲染:
blade
@island(name: 'revenue')
<div class="sidebar">
Revenue: {{ $this->revenue }}
</div>
@endisland
@island(name: 'revenue')
<div class="header">
Revenue: {{ $this->revenue }}
</div>
@endisland
<button type="button" wire:click="$refresh" wire:island="revenue">
Refresh revenue
</button>每当触发一个岛时,两个岛都会一起更新。
追加和前置模式
岛可以追加或前置新内容,而不是完全替换内容。这非常适合分页、无限滚动或实时源:
blade
<?php // resources/views/components/⚡activity-feed.blade.php
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Activity;
new class extends Component
{
public $page = 1;
public function loadMore()
{
$this->page++;
}
#[Computed]
public function activities()
{
return Activity::latest()
->forPage($this->page, 10)
->get();
}
};
?>
<div>
@island(name: 'feed')
@foreach ($this->activities as $activity)
<x-activity-item :activity="$activity" />
@endforeach
@endisland
<button type="button" wire:click.append="loadMore" wire:island="feed">
Load more
</button>
</div>可用模式:
.append- 添加到末尾.prepend- 添加到开头
嵌套岛
岛可以相互嵌套。当外层岛重新渲染时,默认情况下会跳过内层岛:
blade
@island(name: 'revenue')
<div>
Total revenue: {{ $this->revenue }}
@island(name: 'breakdown')
<div>
Monthly breakdown: {{ $this->monthlyBreakdown }}
<button type="button" wire:click="$refresh">
Refresh breakdown
</button>
</div>
@endisland
<button type="button" wire:click="$refresh">
Refresh revenue
</button>
</div>
@endisland点击"Refresh revenue"仅更新外层岛,而"Refresh breakdown"仅更新内层岛。
始终与父级一起渲染
默认情况下,当组件重新渲染时,会跳过岛。使用 always 参数强制岛在父组件更新时更新:
blade
<div>
@island(always: true)
<div>
Revenue: {{ $this->revenue }}
<button type="button" wire:click="$refresh">Refresh revenue</button>
</div>
@endisland
<button type="button" wire:click="$refresh">Refresh</button>
</div>使用 always: true,岛将在组件的任何部分更新时重新渲染。这对于应始终与组件状态保持同步的关键数据很有用。
这也适用于嵌套岛——具有 always: true 的内层岛将在其父岛更新时更新。
跳过初始渲染
skip 参数防止岛最初渲染,非常适合按需内容:
blade
@island(skip: true)
@placeholder
<button type="button" wire:click="$refresh">
Load revenue details
</button>
@endplaceholder
<div>
Revenue: {{ $this->revenue }}
<button type="button" wire:click="$refresh">Refresh</button>
</div>
@endisland占位符内容将首先显示。触发时,岛渲染并替换占位符。
岛轮询
你可以在岛内使用 wire:poll 以按间隔刷新该岛:
blade
@island(skip: true)
<div wire:poll.3s>
Revenue: {{ $this->revenue }}
<button type="button" wire:click="$refresh">Refresh</button>
</div>
@endisland轮询的范围限定于岛——只有岛每 3 秒刷新一次,而不是整个组件。
注意事项
虽然岛提供了强大的隔离,但请记住:
数据范围:岛可以访问组件的属性和方法,但不能访问在岛外定义的模板变量。父模板中的任何 @php 变量或循环变量在岛内都不可用:
blade
@php
$localVariable = 'This won\'t be available in the island';
@endphp
@island
{{-- ❌ This will error - $localVariable is not accessible --}}
{{ $localVariable }}
{{-- ✅ Component properties work fine --}}
{{ $this->revenue }}
@endisland岛不能在循环或条件中使用:因为岛无法访问循环变量或条件上下文,所以它们不能在 @foreach、@if 或其他控制结构内使用:
blade
{{-- ❌ This won't work --}}
@foreach ($items as $item)
@island
{{ $item->name }}
@endisland
@endforeach
{{-- ❌ This won't work either --}}
@if ($showRevenue)
@island
Revenue: {{ $this->revenue }}
@endisland
@endif
{{-- ✅ Instead, put the loop/conditional inside the island --}}
@island
@if ($this->showRevenue)
Revenue: {{ $this->revenue }}
@endif
@foreach ($this->items as $item)
{{ $item->name }}
@endforeach
@endisland状态同步:虽然岛请求并行运行,但岛和根组件都可以修改相同的组件状态。如果多个请求同时进行,可能会出现不同的状态——最后返回的响应将赢得状态之争。
何时使用岛:岛最有益于:
- 不应阻塞初始页面加载的昂贵计算
- 具有自己交互的独立区域
- 仅影响 UI 部分的实时更新
- 大型组件中的性能瓶颈
岛对于静态内容、紧密耦合的 UI 或已经快速渲染的简单组件来说不是必需的。