计算属性

您是视觉学习者吗?
使用我们深入的屏幕录制掌握 Livewire
立即观看

计算属性是创建 Livewire 中的“派生”属性的一种方式。与 Eloquent 模型上的访问器一样,计算属性允许您访问值并将其缓存起来,以便在请求期间将来访问。

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

基本用法

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

确保导入属性类

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

例如,这里有一个 ShowUser 组件,它使用名为 user() 的计算属性来访问基于名为 $userId 的属性的 User Eloquent 模型

<?php
 
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\User;
 
class ShowUser extends Component
{
public $userId;
 
#[Computed]
public function user()
{
return User::find($this->userId);
}
 
public function follow()
{
Auth::user()->follow($this->user);
}
 
public function render()
{
return view('livewire.show-user');
}
}
<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->property 语法在 blade 中访问属性时,将会导致错误。

性能优势

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

以计算属性的形式访问方法比直接调用方法具有性能优势。在内部,当计算属性第一次执行时,Livewire 会缓存返回的值。这样,请求中的任何后续访问都将返回缓存值,而不是多次执行。

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

计算属性仅针对单个请求进行缓存

一个常见的误解是 Livewire 会在页面上缓存 Livewire 组件的整个生命周期内的计算属性。然而,事实并非如此。相反,Livewire 仅为单个组件请求的持续时间缓存结果。这意味着,如果你的计算属性方法包含一个昂贵的数据库查询,那么它将在你的 Livewire 组件执行更新的每次都执行。

清除缓存

考虑以下有问题的场景

  1. 你访问了一个依赖于特定属性或数据库状态的计算属性
  2. 底层属性或数据库状态发生改变
  3. 属性的缓存值变得陈旧,需要重新计算

要清除或“清除”存储的缓存,可以使用 PHP 的 unset() 函数。

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

<?php
 
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;
 
class ShowPosts 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) 使缓存失效。

在请求之间缓存

有时你希望缓存计算属性的值以供 Livewire 组件使用,而不是在每次请求后将其清除。在这些情况下,你可以使用 Laravel 的缓存实用程序

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

<?php
 
use Illuminate\Support\Facades\Cache;
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\User;
 
class ShowUser extends Component
{
public $userId;
 
#[Computed]
public function user()
{
$key = 'user'.$this->getId();
$seconds = 3600; // 1 hour...
 
return Cache::remember($key, $seconds, function () {
return User::find($this->userId);
});
}
 
// ...
}

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

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

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 秒(一小时)。您可以通过将附加的 seconds 参数传递给 #[Computed] 属性来覆盖此默认值

#[Computed(persist: true, seconds: 7200)]
调用 unset() 将破坏此缓存

如前所述,您可以使用 PHP 的 unset() 方法清除计算属性的缓存。这也适用于使用 persist: true 参数的计算属性。在对缓存的计算属性调用 unset() 时,Livewire 不仅会清除计算属性缓存,还会清除 Laravel 缓存中的底层缓存值。

跨所有组件缓存

您可以使用 #[Computed] 属性提供的 cache: true 参数,在整个应用程序中缓存计算属性的值,而不是在单个组件的生命周期内缓存计算属性的值

use Livewire\Attributes\Computed;
use App\Models\Post;
 
#[Computed(cache: true)]
public function posts()
{
return Post::all();
}

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

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

use Livewire\Attributes\Computed;
use App\Models\Post;
 
#[Computed(cache: true, key: 'homepage-posts')]
public function posts()
{
return Post::all();
}

何时使用计算属性?

除了提供性能优势之外,计算属性在以下几种情况下也有帮助。

具体来说,在将数据传递到组件的 Blade 模板时,在某些情况下,计算属性是更好的选择。以下是简单组件的 render() 方法将 posts 集合传递到 Blade 模板的示例

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

虽然这对于许多用例来说已经足够了,但以下三种情况下,计算属性将是更好的选择

有条件地访问值

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

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

<div>
@if (Auth::user()->can_see_posts)
@foreach ($posts as $post)
<!-- ... -->
@endforeach
@endif
</div>

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

以下是使用计算属性的上述场景的版本

use Livewire\Attributes\Computed;
use App\Models\Post;
 
#[Computed]
public function posts()
{
return Post::all();
}
 
public function render()
{
return view('livewire.show-posts');
}
<div>
@if (Auth::user()->can_see_posts)
@foreach ($this->posts as $post)
<!-- ... -->
@endforeach
@endif
</div>

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

使用内联模板

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

以下是内联组件的示例,由于我们在 render() 中直接返回模板字符串,因此我们永远没有机会将数据传递到视图

<?php
 
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
#[Computed]
public function posts()
{
return Post::all();
}
 
public function render()
{
return <<<HTML
<div>
@foreach ($this->posts as $post)
<!-- ... -->
@endforeach
</div>
HTML;
}
}

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

省略 render 方法

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

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

与其在组件中重新引入 render() 方法,不如通过计算属性向视图提供该数据

<?php
 
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
#[Computed]
public function posts()
{
return Post::all();
}
}
<div>
@foreach ($this->posts as $post)
<!-- ... -->
@endforeach
</div>