Skip to content

island

island允许你在 Livewire 组件内创建独立更新的隔离区域。当island内发生操作时,只有该island会重新渲染——而不是整个组件。

这为你提供了将组件拆分为更小部分的性能优势,而无需创建单独的子组件、管理 props 或处理组件通信的开销。

基本用法

要创建island,用 @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()
    {
        // 昂贵的计算或查询...
        return Revenue::yearToDate();
    }
};
blade
<div>
    @island
        <div>
            收入: {{ $this->revenue }}

            <button type="button" wire:click="$refresh">刷新</button>
        </div>
    @endisland

    <div>
        <!-- 其他内容... -->
    </div>
</div>

当点击"刷新"按钮时,只有包含收入计算的island会重新渲染。组件的其余部分保持不变。

因为昂贵的计算在计算属性内——按需评估——它只会在island重新渲染时调用,而不是页面的其他部分更新时。然而,由于island默认随页面加载,revenue 属性仍会在初始页面加载期间计算。

懒加载

有时你有昂贵的计算或缓慢的 API 调用,不应该阻塞初始页面加载。你可以使用 lazy 参数将island的初始渲染推迟到页面加载之后:

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()
    {
        // 昂贵的计算或查询...
        return Revenue::yearToDate();
    }
};
blade
<div>
    @island(lazy: true)
        <div>
            收入: {{ $this->revenue }}

            <button type="button" wire:click="$refresh">刷新</button>
        </div>
    @endisland

    <div>
        <!-- 其他内容... -->
    </div>
</div>

island最初会显示加载状态,然后在单独的请求中获取并渲染其内容。

懒加载 vs 延迟加载

默认情况下,lazy 使用交叉观察器在island进入视口可见时触发加载。如果你希望island在页面加载后立即加载(无论可见性如何),请改用 defer

blade
{{-- 滚动到视图时加载 --}}
@island(lazy: true)
    <!-- ... -->
@endisland

{{-- 页面加载后立即加载 --}}
@island(defer: true)
    <!-- ... -->
@endisland

自定义加载状态

你可以使用 @placeholder 指令自定义懒加载island加载时显示的内容:

blade
@island(lazy: true)
    @placeholder
        <!-- 加载指示器 -->
        <div class="animate-pulse">
            <div class="h-32 bg-gray-200 rounded"></div>
        </div>
    @endplaceholder

    <div>
        收入: {{ $this->revenue }}

        <button type="button" wire:click="$refresh">刷新</button>
    </div>
@endisland

命名island

要从组件的其他位置触发island,给它一个名称并使用 wire:island 引用它:

blade
<div>
    @island(name: 'revenue')
        <div>
            收入: {{ $this->revenue }}
        </div>
    @endisland

    <button type="button" wire:click="$refresh" wire:island="revenue">
        刷新收入
    </button>
</div>

wire:island 指令与 wire:clickwire:submit 等操作指令一起工作,将其更新范围限定到特定island

当你有多个同名的island时,它们会链接在一起并始终作为一组渲染:

blade
@island(name: 'revenue')
    <div class="sidebar">
        收入: {{ $this->revenue }}
    </div>
@endisland

@island(name: 'revenue')
    <div class="header">
        收入: {{ $this->revenue }}
    </div>
@endisland

<button type="button" wire:click="$refresh" wire:island="revenue">
    刷新收入
</button>

每当触发其中一个island时,两个island都会一起更新。

追加和前置模式

island可以追加或前置新内容,而不是完全替换内容。这非常适合分页、无限滚动或实时信息流:

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();
    }
};
blade
<div>
    @island(name: 'feed')
        @foreach ($this->activities as $activity)
            <x-activity-item wire:key="{{ $activity->id }}" :activity="$activity" />
        @endforeach
    @endisland

    <button type="button" wire:click="loadMore" wire:island.append="feed">
        加载更多
    </button>
</div>

可用模式:

  • wire:island.append - 添加到末尾
  • wire:island.prepend - 添加到开头

嵌套island

island可以相互嵌套。当外部island重新渲染时,默认情况下会跳过内部island

blade
@island(name: 'revenue')
    <div>
        总收入: {{ $this->revenue }}

        @island(name: 'breakdown')
            <div>
                月度明细: {{ $this->monthlyBreakdown }}

                <button type="button" wire:click="$refresh">
                    刷新明细
                </button>
            </div>
        @endisland

        <button type="button" wire:click="$refresh">
            刷新收入
        </button>
    </div>
@endisland

点击"刷新收入"只更新外部island,而"刷新明细"只更新内部island

始终与父组件一起渲染

默认情况下,当组件重新渲染时,island会被跳过。使用 always 参数强制island在父组件更新时一起更新:

blade
<div>
    @island(always: true)
        <div>
            收入: {{ $this->revenue }}

            <button type="button" wire:click="$refresh">刷新收入</button>
        </div>
    @endisland

    <button type="button" wire:click="$refresh">刷新</button>
</div>

使用 always: true,每当组件的任何部分更新时,island都会重新渲染。这对于应该始终与组件状态保持同步的关键数据很有用。

这也适用于嵌套island——带有 always: true 的内部island会在其父island更新时一起更新。

跳过初始渲染

skip 参数阻止island最初渲染,非常适合按需内容:

blade
@island(skip: true)
    @placeholder
        <button type="button" wire:click="$refresh">
            加载收入详情
        </button>
    @endplaceholder

    <div>
        收入: {{ $this->revenue }}

        <button type="button" wire:click="$refresh">刷新</button>
    </div>
@endisland

占位符内容最初会显示。触发时,island渲染并替换占位符。

island轮询

你可以在island内使用 wire:poll 仅按间隔刷新该island

blade
@island(skip: true)
    <div wire:poll.3s>
        收入: {{ $this->revenue }}

        <button type="button" wire:click="$refresh">刷新</button>
    </div>
@endisland

轮询范围限定在island——只有island每 3 秒刷新一次,而不是整个组件。

注意事项

虽然island提供了强大的隔离,但请记住:

数据范围island可以访问组件的属性和方法,但不能访问在island外部定义的模板变量。父模板中的任何 @php 变量或循环变量在island内部都不可用:

blade
@php
    $localVariable = '这在`island`中不可用';
@endphp

@island
    {{-- ❌ 这会报错 - $localVariable 不可访问 --}}
    {{ $localVariable }}

    {{-- ✅ 组件属性正常工作 --}}
    {{ $this->revenue }}
@endisland

island不能在循环或条件语句中使用:因为island无法访问循环变量或条件上下文,它们不能在 @foreach@if 或其他控制结构内使用:

blade
{{-- ❌ 这不起作用 --}}
@foreach ($items as $item)
    @island
        {{ $item->name }}
    @endisland
@endforeach

{{-- ❌ 这也不起作用 --}}
@if ($showRevenue)
    @island
        收入: {{ $this->revenue }}
    @endisland
@endif

{{-- ✅ 相反,将循环/条件放在`island`内部 --}}
@island
    @if ($this->showRevenue)
        收入: {{ $this->revenue }}
    @endif

    @foreach ($this->items as $item)
        {{ $item->name }}
    @endforeach
@endisland

状态同步:虽然island请求并行运行,但island和根组件都可以改变相同的组件状态。如果多个请求同时在飞行中,可能会有分歧的状态——最后返回的响应将赢得状态之战。

何时使用islandisland最适用于:

  • 不应阻塞初始页面加载的昂贵计算
  • 具有自己交互的独立区域
  • 仅影响 UI 部分的实时更新
  • 大型组件中的性能瓶颈

island对于静态内容、紧密耦合的 UI 或已经快速渲染的简单组件不是必需的。

另请参阅

  • 嵌套 — 使用子组件的替代方法
  • 懒加载 — 延迟加载昂贵的内容
  • 计算属性 — 使用记忆化优化island性能
  • @island — 创建隔离的更新区域