Вычисляемые свойства⚓︎
Вычисляемые свойства — это способ создания «производных» свойств в Livewire. Подобно аксессорам в модели Eloquent, вычисляемые свойства позволяют получать значения и кэшировать их для последующего обращения в рамках одного запроса.
Вычисляемые свойства особенно полезны в сочетании с публичными свойствами компонента.
Базовое использование⚓︎
Чтобы создать вычисляемое свойство, добавьте атрибут #[Computed] над любым методом в вашем Livewire-компоненте. После этого к методу можно обращаться как к обычному свойству.
Не забудьте импортировать класс атрибута
Убедитесь, что импортировали нужный класс атрибута. Например, для #[Computed] требуется импорт: use Livewire\Attributes\Computed;.
Например, вот компонент show-user, который использует вычисляемое свойство user() для получения модели User на основе свойства $userId:
<?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);
}
};
<div>
<h1>{{ $this->user->name }}</h1>
<span>{{ $this->user->email }}</span>
<button wire:click="follow">Следовать</button>
</div>
Поскольку к методу user() добавлен атрибут #[Computed], его значение становится доступным в других методах компонента и внутри Blade-шаблона.
В шаблоне обязательно использовать $this
В отличие от обычных свойств, вычисляемые свойства недоступны напрямую в шаблоне компонента. Вместо этого к ним нужно обращаться через объект $this. Например, вычисляемое свойство posts() в шаблоне должно использоваться как $this->posts.
Вычисляемые свойства не поддерживаются в объектах Livewire\Form
Попытка использовать вычисляемое свойство внутри формы приведёт к ошибке при обращении к свойству в Blade через синтаксис $form->property.
Преимущество в производительности⚓︎
Вы можете задаться вопросом: зачем вообще использовать вычисляемые свойства? Почему бы просто не вызывать метод напрямую?
Обращение к методу как к вычисляемому свойству даёт преимущество в производительности по сравнению с прямым вызовом метода. Внутри Livewire при первом обращении к вычисляемому свойству значение кэшируется (memoized). Благодаря этому все последующие обращения в рамках одного запроса возвращают уже сохранённое значение, а не выполняют код заново.
Это позволяет свободно использовать производные значения, не беспокоясь о влиянии на производительность.
Кэширование вычисляемых свойств действует только в пределах одного запроса
Распространённое заблуждение — думать, что Livewire сохраняет кэш вычисляемых свойств на всём протяжении жизни компонента на странице. На самом деле это не так. Кэш существует только в течение одного запроса к компоненту (он не сохраняется между запросами). Это означает, что если ваш метод вычисляемого свойства содержит тяжёлый запрос к базе данных, он будет выполняться заново при каждом обновлении компонента.
Сброс кэша⚓︎
Рассмотрим проблемный сценарий:
- Вы обращаетесь к вычисляемому свойству, которое зависит от определённого свойства или состояния базы данных
- Это свойство или состояние базы данных изменяется
- Сохранённое (закэшированное) значение становится устаревшим и требует пересчёта
Чтобы сбросить сохранённый кэш, можно воспользоваться функцией PHP unset().
Ниже приведён пример метода createPost(), который при создании новой записи делает вычисляемое свойство posts() устаревшим — то есть posts() нужно пересчитать, чтобы учесть только что добавленную запись:
<?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).
Кэширование между запросами⚓︎
Сравнение мемоизации и кэширования
Мемоизация, о котором шла речь выше, действует только в пределах одного запроса. Если вам нужно, чтобы значения сохранялись между несколькими запросами, требуется полноценное кэширование Laravel.
Иногда требуется кэшировать значение вычисляемого свойства на протяжении всего времени жизни компонента Livewire, а не сбрасывать его после каждого запроса. В таких случаях можно воспользоваться утилитами кэширования Laravel.
Ниже приведён пример вычисляемого свойства user(), в котором вместо прямого выполнения запроса Eloquent мы оборачиваем его в Cache::remember(), чтобы при последующих запросах значение бралось из кэша Laravel, а не выполнялся запрос заново:
<?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 имеет уникальный идентификатор, мы можем использовать $this->getId() для создания уникального ключа кэша, который будет применяться только к последующим запросам именно этого экземпляра компонента.
Однако, как вы могли заметить, большая часть этого кода предсказуема и легко поддаётся абстрагированию. Именно поэтому атрибут #[Computed] в Livewire предоставляет удобный параметр 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 секунд (один час). Вы можете переопределить это значение по умолчанию, передав дополнительный параметр seconds в атрибут #[Computed]:
Вызов unset() очищает и мемо, и кэш
Как обсуждалось ранее, вы можете очистить мемо (результат мемоизации) вычисляемого свойства с помощью метода unset() в PHP. Это также применимо к вычисляемым свойствам с параметром persist: true. При вызове unset() на сохраняемом вычисляемом свойстве Livewire очистит не только мемо в рамках запроса, но и само закэшированное значение в кэше Laravel.
Кэширование для всех компонентов⚓︎
Вместо кэширования значения вычисляемого свойства только на время жизни одного компонента, вы можете кэшировать значение вычисляемого свойства для всех компонентов в вашем приложении, используя параметр cache: true атрибута #[Computed]:
<?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-шаблон:
<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<!-- ... -->
</div>
@endforeach
</div>
Хотя этого достаточно для многих случаев использования, вот три сценария, в которых вычисляемое свойство будет лучшей альтернативой:
Условный доступ к значениям⚓︎
Если в вашем 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');
}
<div>
@if (Auth::user()->can_see_posts)
@foreach ($this->posts as $post)
<div wire:key="{{ $post->id }}">
<!-- ... -->
</div>
@endforeach
@endif
</div>
Теперь, поскольку мы предоставляем посты в шаблон через вычисляемое свойство, запрос к базе данных выполняется только тогда, когда данные действительно нужны.
Использование inline-шаблонов⚓︎
Ещё один сценарий, когда вычисляемые свойства оказываются полезны, — это использование inline-шаблонов в вашем компоненте.
Ниже приведён пример inline-компонента, в котором, поскольку мы возвращаем строку шаблона напрямую внутри метода render(), у нас нет возможности передать данные в представление:
<?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
use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
#[Computed]
public function posts()
{
return Post::all();
}
};
<div>
@foreach ($this->posts as $post)
<div wire:key="{{ $post->id }}">
<!-- ... -->
</div>
@endforeach
</div>
Альтернатива: Свойства сессии⚓︎
Если вам нужно сохранять простые значения между обновлениями страницы без кэширования между запросами, вместо вычисляемых свойств лучше использовать атрибут #[Session].
Свойства сессии полезны, когда:
- Вы хотите, чтобы значения, специфичные для пользователя, сохранялись при перезагрузке страницы (например, фильтры поиска или настройки интерфейса)
- Значение не должно передаваться через URL
- Значение простое и его хранение не требует значительных вычислительных затрат
Например, сохранение поискового запроса в сессии:
Это позволяет сохранять значение поиска при обновлении страницы без использования параметров URL или кэширования вычисляемых свойств.
Подробнее о свойствах сессии →
См. также⚓︎
- Свойства — Основы управления свойствами
- Островки — Оптимизация производительности с отложенными вычисляемыми значениями
- Атрибут Computed — Использование #[Computed] для мемоизации
- Компоненты — Доступ к вычисляемым свойствам в представлениях