主题
计算属性
计算属性是在 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 中尝试使用计算属性将导致错误,当你尝试在 blade 中使用 $form->property 语法访问该属性时。
性能优势
你可能会问自己:为什么要使用计算属性?为什么不直接调用方法?
将方法作为计算属性访问比调用方法提供了性能优势。在内部,当计算属性第一次执行时,Livewire 会缓存返回的值。这样,请求中的任何后续访问都将返回缓存的值,而不是多次执行。
这允许你自由地访问派生值,而不用担心性能影响。
计算属性仅在单个请求期间缓存
一个常见的误解是 Livewire 在页面上 Livewire 组件的整个生命周期内缓存计算属性。然而,事实并非如此。相反,Livewire 只在单个组件请求的持续时间内缓存结果。这意味着如果你的计算属性方法包含昂贵的数据库查询,它将在你的 Livewire 组件每次执行更新时都会被执行。
清除缓存
考虑以下问题场景:
- 你访问了一个依赖于某个属性或数据库状态的计算属性
- 底层属性或数据库状态发生变化
- 该属性的缓存值变得过时,需要重新计算
要清除或“清空”存储的缓存,你可以使用 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。为了确保在视图内访问时 $this->posts 包含最新内容,使用 unset($this->posts) 使缓存无效。
跨请求缓存
有时你希望在 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)
<!-- ... -->
@endforeach
</div>虽然这对于许多用例来说已经足够,但以下是三种计算属性会是更好选择的场景:
条件访问值
如果你在 Blade 模板中条件地访问一个检索起来计算成本很高的值,你可以使用计算属性来减少性能开销。
考虑以下没有使用计算属性的模板:
blade
<div>
@if (Auth::user()->can_see_posts)
@foreach ($posts as $post)
<!-- ... -->
@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)
<!-- ... -->
@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)
<!-- ... -->
@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)
<!-- ... -->
@endforeach
</div>