Skip to content
全部文档

岛屿(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 使用交叉观察器(intersection observer),在岛屿进入视口可见时触发加载。若希望岛屿在页面加载后立即加载(无论是否可见),请改用 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:clickwire: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 wire:key="{{ $activity->id }}" :activity="$activity" />
        @endforeach
    @endisland

    <button type="button" wire:click="loadMore" wire:island.append="feed">
        Load more
    </button>
</div>

可用模式:

  • wire:island.append - 追加到末尾
  • wire:island.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 秒只会刷新该岛屿,而不是整个组件。

从 JavaScript 触发岛屿

wire:island 指令只能与 wire:click 等 Livewire 操作指令一起使用。若要从 Alpine 或 JavaScript 将操作限定到某个岛屿,请使用 $wire.$island()

blade
<button type="button" x-on:click="$wire.$island('feed').loadMore()">
    Load more
</button>

这等价于 wire:click="loadMore" wire:island="feed",但能使用 Alpine 表达式与 JavaScript 逻辑,更灵活。

追加与前置模式可通过 options 参数支持:

blade
<button type="button" x-on:click="$wire.$island('feed', { mode: 'append' }).loadMore()">
    Load more
</button>

任何 $wire 方法都可与 $island() 配合使用,包括 $refresh()$set()$toggle()

blade
<button type="button" x-on:click="$wire.$island('revenue').$refresh()">
    Refresh revenue
</button>

注意事项

虽然岛屿提供了强大的隔离能力,但请注意:

数据作用域: 岛屿可以访问组件的属性和方法,但无法访问岛屿外定义的模板变量。父模板中的任何 @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,或已经渲染很快的简单组件,不必使用岛屿。

另见