Skip to content
全部文档

计算属性

计算属性是在 Livewire 中创建「派生」属性的方式。类似于 Eloquent 模型上的访问器,计算属性让你访问值,并在本次请求中将其 memoize(记忆化),供后续访问复用。

计算属性与组件的公共属性搭配使用时尤其有用。

基本用法

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

WARNING

请确保导入 Attribute 类

请确保导入所用的 Attribute 类。例如,下面的 #[Computed] Attribute 需要导入: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>

由于 user() 方法已添加 #[Computed] Attribute,该值可在组件的其他方法以及 Blade 模板中访问。

INFO

模板中必须使用 $this

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

WARNING

计算属性不支持用于 Livewire\Form 对象。

若在 Form 中使用计算属性,并在 Blade 中用 $form->property 语法访问,会报错。

性能优势

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

把方法当作计算属性访问,比直接调用方法更有性能优势。在内部,计算属性首次执行时,Livewire 会 memoize 返回值。这样,同一次请求中的后续访问会返回已记忆的值,而不会多次执行。

这样你可以自由访问派生值,而不必担心性能影响。

WARNING

计算属性仅在单次请求中 memoize

一个常见误解是:Livewire 会在页面上 Livewire 组件的整个生命周期内 memoize 计算属性。实际上并非如此。Livewire 只在单次组件请求期间 memoize 结果(不会跨请求持久化)。这意味着,如果计算属性方法包含昂贵的数据库查询,每次 Livewire 组件执行更新时都会再次执行该查询。

清除 memo

考虑下面这个有问题的场景:

  1. 你访问了一个依赖某属性或数据库状态的计算属性
  2. 底层属性或数据库状态发生变化
  3. 该属性的 memoize 值变得过时,需要重新计算

要清除或「作废」已存储的 memo,可以使用 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); // [tl! highlight]
    }

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

    // ...
};

在上面的组件中,由于 createPost() 在创建新帖子之前就访问了 $this->posts,计算属性会在创建新帖子之前被 memoize。为确保在视图中访问时 $this->posts 包含最新内容,需用 unset($this->posts) 清除 memo。

跨请求缓存

TIP

Memoization 与 Caching

到目前为止讨论的 memoization 只持续单次请求。若需要值在多次请求间持久保存,则需要真正的 Laravel 缓存。

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

下面是一个名为 userName() 的计算属性示例:我们不直接执行 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 userName()
    {
        $key = 'user-name'.$this->getId();
        $seconds = 3600; // 1 hour...

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

    // ...
};

由于每个 Livewire 组件实例都有唯一 ID,我们可以用 $this->getId() 生成唯一的缓存键,该键仅适用于同一组件实例的后续请求。

不过你可能已经注意到,这段代码大多是可预期的,很容易抽象。因此,Livewire 的 #[Computed] Attribute 提供了方便的 persist 参数。给方法加上 #[Computed(persist: true)],无需额外代码即可达到同样效果:

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

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

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

Livewire 会将持久化值缓存 3600 秒(一小时)。你可以通过向 #[Computed] Attribute 额外传入 seconds 参数来覆盖该默认值:

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

WARNING

在 Laravel 13 上缓存对象

新建的 Laravel 13 应用只会从缓存反序列化明确允许的 PHP 类。当 Laravel 无法还原持久化或缓存计算属性返回的对象时,Livewire 会重新求值该属性,以保证值仍然正确。在调试模式下,Livewire 还会向应用日志写入警告,因为该值并非从缓存提供。

优先缓存标量或数组。若要有意缓存对象,请将其对象图中的每个类都加入 config/cache.phpcache.serializable_classes

php
'serializable_classes' => [
    App\Models\User::class,
],

TIP

调用 unset() 会同时清除 memo 与缓存

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

跨所有组件缓存

除了在单个组件生命周期内缓存计算属性的值,你还可以使用 #[Computed] Attribute 提供的 cache: true 参数,在应用的所有组件间缓存计算属性的值:

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

#[Computed(cache: true)]
public function postTitles()
{
    return Post::query()->pluck('title', 'id')->all();
}

在上面的示例中,在缓存过期或被作废之前,应用中该组件的每个实例都会共享 $this->postTitles 的同一缓存值。

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

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

#[Computed(cache: true, key: 'homepage-posts')]
public function postTitles()
{
    return Post::query()->pluck('title', 'id')->all();
}

何时使用计算属性?

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

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

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 视图。

在这种情况下,你显然没有可用来向 Blade 视图传递数据的 render() 方法。

与其把 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] Attribute,而不是计算属性。

Session 属性适用于以下情况:

  • 你希望用户专属值在页面重载后仍然保留(例如搜索过滤条件或 UI 偏好)
  • 你不需要通过 URL 分享该值
  • 该值简单,存储成本不高

例如,将搜索词存入 session:

php
use Livewire\Attributes\Session;

#[Session]
public $search = '';

这样可在页面刷新后保留搜索值,而无需使用 URL 参数或计算属性缓存。

了解更多关于 Session 属性 →

另见