Skip to content

计算属性

计算属性是在 Livewire 中创建"派生"属性的一种方式。就像 Eloquent 模型上的访问器一样,计算属性允许你访问值并在请求期间将其记忆化以供将来访问。

计算属性在与组件的公共属性结合使用时特别有用。

基本用法

要创建计算属性,你可以在 Livewire 组件的任何方法上方添加 #[Computed] 属性。一旦该属性被添加到方法上,你就可以像访问任何其他属性一样访问它。

确保导入属性类

确保导入任何属性类。例如,下面的 #[Computed] 属性需要以下导入 use Livewire\Attributes\Computed;

例如,这是一个 show-user 组件,它使用名为 user() 的计算属性根据名为 $userId 的属性访问 User Eloquent 模型:

php
<?php // resources/views/components/⚡show-user.blade.php

use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\User;

new class extends Component {
    public $userId;

    #[Computed]
    public function user()
    {
        return User::find($this->userId);
    }

    public function follow()
    {
        Auth::user()->follow($this->user);
    }
};
blade
<div>
    <h1>{{ $this->user->name }}</h1>

    <span>{{ $this->user->email }}</span>

    <button wire:click="follow">Follow</button>
</div>

因为 #[Computed] 属性已添加到 user() 方法,该值可以在组件的其他方法和 Blade 模板中访问。

必须在模板中使用 $this

与普通属性不同,计算属性不能直接在组件模板中使用。相反,你必须在 $this 对象上访问它们。例如,名为 posts() 的计算属性必须在模板中通过 $this->posts 访问。

计算属性在 Livewire\Form 对象上不受支持。

尝试在 Form 中使用计算属性,当你尝试使用 $form->property 语法在 blade 中访问该属性时会导致错误。

性能优势

你可能会问自己:为什么要使用计算属性?为什么不直接调用方法?

将方法作为计算属性访问比直接调用方法提供了性能优势。在内部,当计算属性第一次执行时,Livewire 会记忆化返回的值。这样,请求中的任何后续访问将返回记忆化的值而不是多次执行。

这允许你自由地访问派生值而不必担心性能影响。

计算属性仅在单个请求中记忆化

一个常见的误解是 Livewire 会在页面上 Livewire 组件的整个生命周期中记忆化计算属性。然而,情况并非如此。相反,Livewire 只在单个组件请求的持续时间内记忆化结果(它不会在请求之间持久化)。这意味着如果你的计算属性方法包含昂贵的数据库查询,它将在每次 Livewire 组件执行更新时执行。

清除记忆

考虑以下问题场景:

  1. 你访问一个依赖于某个属性或数据库状态的计算属性
  2. 底层属性或数据库状态发生变化
  3. 属性的记忆化值变得陈旧,需要重新计算

要清除或"破坏"存储的记忆,你可以使用 PHP 的 unset() 函数。

下面是一个名为 createPost() 的操作示例,通过在应用程序中创建新帖子,使 posts() 计算属性变得陈旧——这意味着计算属性 posts() 需要重新计算以包含新添加的帖子:

php
<?php // resources/views/components/⚡show-posts.blade.php

use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;

new class extends Component {
    public function createPost()
    {
        if ($this->posts->count() > 10) {
            throw new \Exception('Maximum post count exceeded');
        }

        Auth::user()->posts()->create(...);

        unset($this->posts);
    }

    #[Computed]
    public function posts()
    {
        return Auth::user()->posts;
    }

    // ...
};

在上面的组件中,计算属性在创建新帖子之前被记忆化,因为 createPost() 方法在创建新帖子之前访问了 $this->posts。为了确保在视图中访问时 $this->posts 包含最新的内容,使用 unset($this->posts) 清除记忆。

跨请求缓存

记忆化 vs 缓存

我们到目前为止讨论的记忆化只持续单个请求。如果你需要值在多个请求之间持久化,你需要实际的 Laravel 缓存。

有时你可能希望在 Livewire 组件的整个生命周期内缓存计算属性的值,而不是在每次请求后清除它。在这些情况下,你可以使用 Laravel 的缓存工具

下面是一个名为 user() 的计算属性示例,我们不是直接执行 Eloquent 查询,而是将查询包装在 Cache::remember() 中,以确保任何未来的请求从 Laravel 的缓存中检索它,而不是重新执行查询:

php
<?php // resources/views/components/⚡show-user.blade.php

use Illuminate\Support\Facades\Cache;
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\User;

new class extends Component {
    public $userId;

    #[Computed]
    public function user()
    {
        $key = 'user'.$this->getId();
        $seconds = 3600; // 1 小时...

        return Cache::remember($key, $seconds, function () {
            return User::find($this->userId);
        });
    }

    // ...
};

因为 Livewire 组件的每个唯一实例都有一个唯一的 ID,我们可以使用 $this->getId() 生成一个唯一的缓存键,该键仅应用于同一组件实例的未来请求。

但是,正如你可能已经注意到的,这段代码的大部分是可预测的,可以轻松抽象。因此,Livewire 的 #[Computed] 属性提供了一个有用的 persist 参数。通过将 #[Computed(persist: true)] 应用于方法,你可以在没有任何额外代码的情况下实现相同的结果:

php
use Livewire\Attributes\Computed;
use App\Models\User;

#[Computed(persist: true)]
public function user()
{
    return User::find($this->userId);
}

在上面的示例中,当从组件访问 $this->user 时,它将继续为页面上 Livewire 组件的持续时间被缓存。这意味着实际的 Eloquent 查询只会执行一次。

Livewire 缓存持久化值 3600 秒(一小时)。你可以通过向 #[Computed] 属性传递额外的 seconds 参数来覆盖此默认值:

php
#[Computed(persist: true, seconds: 7200)]

调用 unset() 将清除记忆和缓存

如前所述,你可以使用 PHP 的 unset() 方法清除计算属性的记忆。这也适用于使用 persist: true 参数的计算属性。当对持久化的计算属性调用 unset() 时,Livewire 不仅会清除请求内的记忆,还会清除 Laravel 缓存中的底层缓存值。

跨所有组件缓存

你可以使用 #[Computed] 属性提供的 cache: true 参数,将计算属性的值缓存到应用程序中的所有组件,而不是仅在单个组件生命周期的持续时间内缓存:

php
use Livewire\Attributes\Computed;
use App\Models\Post;

#[Computed(cache: true)]
public function posts()
{
    return Post::all();
}

在上面的示例中,在缓存过期或被破坏之前,应用程序中此组件的每个实例将共享 $this->posts 的相同缓存值。

如果你需要手动清除计算属性的缓存,你可以使用 key 参数设置自定义缓存键:

php
use Livewire\Attributes\Computed;
use App\Models\Post;

#[Computed(cache: true, key: 'homepage-posts')]
public function posts()
{
    return Post::all();
}

何时使用计算属性?

除了提供性能优势外,还有一些其他场景中计算属性很有帮助。

具体来说,当向组件的 Blade 模板传递数据时,有几种情况下计算属性是更好的选择。下面是一个简单组件的 render() 方法的示例,它将 posts 集合传递给 Blade 模板:

php
public function render()
{
    return view('livewire.show-posts', [
        'posts' => Post::all(),
    ]);
}
blade
<div>
    @foreach ($posts as $post)
        <div wire:key="{{ $post->id }}">
            <!-- ... -->
        </div>
    @endforeach
</div>

虽然这对许多用例来说已经足够,但以下是计算属性会是更好选择的三种场景:

条件性访问值

如果你在 Blade 模板中条件性地访问一个计算成本高昂的值,你可以使用计算属性来减少性能开销。

考虑以下没有计算属性的模板:

blade
<div>
    @if (Auth::user()->can_see_posts)
        @foreach ($posts as $post)
            <div wire:key="{{ $post->id }}">
                <!-- ... -->
            </div>
        @endforeach
    @endif
</div>

如果用户被限制查看帖子,检索帖子的数据库查询已经执行了,但帖子从未在模板中使用。

这是使用计算属性替代的上述场景的版本:

php
use Livewire\Attributes\Computed;
use App\Models\Post;

#[Computed]
public function posts()
{
    return Post::all();
}

public function render()
{
    return view('livewire.show-posts');
}
blade
<div>
    @if (Auth::user()->can_see_posts)
        @foreach ($this->posts as $post)
            <div wire:key="{{ $post->id }}">
                <!-- ... -->
            </div>
        @endforeach
    @endif
</div>

现在,因为我们使用计算属性向模板提供帖子,我们只在需要数据时执行数据库查询。

使用内联模板

计算属性有用的另一个场景是在组件中使用内联模板

下面是一个内联组件的示例,因为我们直接在 render() 中返回模板字符串,我们从来没有机会向视图传递数据:

php
<?php // resources/views/components/⚡show-posts.blade.php

use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[Computed]
    public function posts()
    {
        return Post::all();
    }

    public function render()
    {
        return <<<HTML
        <div>
            @foreach ($this->posts as $post)
                <div wire:key="{{ $post->id }}">
                    <!-- ... -->
                </div>
            @endforeach
        </div>
        HTML;
    }
};

在上面的示例中,如果没有计算属性,我们将无法显式地向 Blade 模板传递数据。

省略 render 方法

在 Livewire 中,减少组件样板代码的另一种方法是完全省略 render() 方法。省略时,Livewire 将使用自己的 render() 方法,按照约定返回相应的 Blade 视图。

在这些情况下,你显然没有 render() 方法可以从中向 Blade 视图传递数据。

与其在组件中重新引入 render() 方法,你可以改为通过计算属性向视图提供该数据:

php
<?php // resources/views/components/⚡show-posts.blade.php

use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[Computed]
    public function posts()
    {
        return Post::all();
    }
};
blade
<div>
    @foreach ($this->posts as $post)
        <div wire:key="{{ $post->id }}">
            <!-- ... -->
        </div>
    @endforeach
</div>

替代方案:Session 属性

如果你需要在页面刷新之间持久化简单值,而不需要跨请求缓存,可以考虑使用 #[Session] 属性而不是计算属性。

Session 属性在以下情况下很有用:

  • 你希望用户特定的值在页面重新加载时持久化(如搜索过滤器或 UI 偏好)
  • 你不需要通过 URL 共享值
  • 值很简单,存储成本不高

例如,在 session 中存储搜索查询:

php
use Livewire\Attributes\Session;

#[Session]
public $search = '';

这会在页面刷新时保持搜索值,而不需要使用 URL 参数或计算属性缓存。

了解更多关于 session 属性 →

另请参阅

  • 属性 — 了解基本属性管理
  • 岛屿 — 使用懒加载计算值优化性能
  • Computed 属性 — 使用 #[Computed] 进行记忆化
  • 组件 — 在视图中访问计算属性