属性

未匹配的标注
本文档最新版为 2.x,旧版本可能放弃维护,推荐阅读最新版!

属性

属性用于在 Livewire 组件中存储和管理数据。必须为组件类上的公共属性,并且可以在服务器端和客户端上访问和修改。

初始化属性

您可以在组件的 mount() 方法内设置属性的初始值。

<?php

namespace App\Livewire;

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

class TodoList extends Component
{
    public $todos = [];

    public $todo = "";

    public function mount()
    {
        $this->todos = Auth::user()->todos;
    }
    // ...
}

在此示例中,我们定义了一个空的todos数组,并使用已认证用户的现有 todos 初始化。当组件首次渲染时,数据库中的所有现有 todos 都会显示给用户。

批量赋值

有时在 mount() 方法中初始化许多属性会很冗长。为了解决这个问题,Livewire 提供了一个便捷的方法,通过 fill() 方法同时赋值多个属性。通过传递一个包含属性名和对应值的关联数组,可以同时设置多个属性,减少 mount() 方法中的重复代码。

<?php

namespace App\Livewire;

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

class UpdatePost extends Component
{
    public $post;

    public $title;

    public $description;

    public function mount(Post $post)
    {
        $this->post = $post;

        $this->fill(
            $post->only('title', 'description'),
        );
    }
    // ...
}

$post->only(...) 返回传入的名称对应的模型属性和值组成的关联数组, $title$description 属性则被初始化为数据库中的 $post 模型的标题和描述,而无需单独设置每个属性。

数据绑定

Livewire 支持通过 wire:modelHTML 属性进行数据双向绑定。可以轻松同步组件属性和 HTML 之间的数据,保持用户界面和组件状态同步。

使用 wire:model 指令将 TodoList 组件中的 $todo 属性绑定到 Input 元素:

<?php

namespace App\Livewire;

use Livewire\Component;

class TodoList extends Component
{
    public $todos = [];

    public $todo = '';

    public function add()
    {
        $this->todos[] = $this->todo;

        $this->todo = '';
    }

    // ...
}
<div>
    <input type="text" wire:model="todo" placeholder="Todo...">

    <button wire:click="add">Add Todo</button>

    <ul>
        @foreach ($todos as $todo)
            <li>{{ $todo }}</li>
        @endforeach
    </ul>
</div>

在上面的示例中,当单击 “Add Todo” 按钮时,文本输入的值将与服务器上的 $todo 属性同步。
这只是wire:model的基本用法。有关更深入的数据绑定信息,请查看我们的表单文档。

重置属性

有时,用户执行操作后,您可能需要将属性重置回其初始状态。在这些情况下,Livewire 提供了一个 reset() 方法,该方法接受一个或多个属性名称,并将它们的值重置为其初始状态。

在下面的示例中,在用户单击 “Add Todo” 按钮后使用 $this->reset('todo')重置 todo 字段:

<?php

namespace App\Livewire;

use Livewire\Component;

class ManageTodos extends Component
{
    public $todos = [];

    public $todo = '';

    public function addTodo()
    {
        $this->todos[] = $this->todo;

        $this->reset('todo');
    }
    // ...
}

支持的属性类型

由于 Livewire 在处理组件数据的方式是独特的,所以它只支持有限一组属性类型。
Livewire 组件中的每个属性都会在请求之间被序列化为 JSON,然后在下一次请求中从 JSON 转换回 PHP 类型。
这个双向转换过程具有一定的限制,也就导致了 Livewire 可以处理的属性类型是有限的。

原始类型

Livewire 支持原始类型,如字符串、整数等。这些类型可以轻松地转换为 JSON,使它们成为 Livewire 组件中理想的属性类型。
Livewire 还支持以下原始属性类型:Array(数组)、String(字符串)、Integer(整数)、Float(浮点数)、Boolean(布尔值)和 Null(空值)。

class TodoList extends Component
{
    public $todos = []; // 数组

    public $todo = ''; // 字符串

    public $maxTodos = 10; // 整数

    public $showTodos = false; // 布尔值

    public $todoFilter; // 空值
}

常见的PHP类型

除了原始类型,Livewire 还支持在 Laravel 应用程序中常见 PHP 对象类型。但需要注意的是,这些类型将在每个请求中被序列化为 JSON,然后在下一次请求中转换回 PHP。这意味着该属性可能不会保留运行时的值,例如闭包。此外对象的信息,例如类名,可能会被暴露给JavaScript。
支持的 PHP 类型如下:

类型 完整类名
Collection Illuminate\Support\Collection
Eloquent Collection Illuminate\Database\Eloquent\Collection
Model Illuminate\Database\Model
DateTime DateTime
Carbon Carbon\Carbon
Stringable Illuminate\Support\Stringable
public function mount()
{
    $this->todos = collect([]); // Collection

    $this->todos = Todos::all(); // Eloquent Collection

    $this->todo = Todos::first(); // Model

    $this->date = new DateTime('now'); // DateTime

    $this->date = new Carbon('now'); // Carbon

    $this->todo = str(''); // Stringable
}

支持自定义类型

Livewire通过两种强大的机制使应用程序支持自定义类型:

  • Wireables
  • Synthesizers

Wireables 对于大多数应用程序来说简单且易于使用,因此我们将在下面探讨它们。如果您是一个高级用户或扩展包作者,想要更灵活性,那么 Synthesizers 是更好的选择。

Wireables

Wireables 是应用程序中实现 Wireable 接口的任何类。
例如,假设您的应用程序中有一个 Customer 对象,其中包含有关客户的主要数据:

class Customer
{
    protected $name;
    protected $age;

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

使用此类的实例设置为 Livewire 组件属性将导致错误,提示您 Customer 属性类型不受支持:

class ShowCustomer extends Component
{
    public Customer $customer;

    public function mount()
    {
        $this->customer = new Customer('Caleb', 29);
    }
}

然而,您可以通过实现 Wireable 接口并在类中添加 toLivewire()fromLivewire() 方法来解决此问题。这些方法告诉 Livewire 如何将此类型的属性转换为 JSON,然后再转换回 PHP:

use Livewire\Wireable;

class Customer implements Wireable
{
    protected $name;
    protected $age;

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    public function toLivewire()
    {
        return [
            'name' => $this->name,
            'age' => $this->age,
        ];
    }

    public static function fromLivewire($data)
    {
        $name = $data['name'];
        $age = $data['age'];

        return new static($name, $age);
    }
}

现在,您可以自由地在 Livewire 组件上设置 Customer 对象。
如果要更全局和强大地支持类型,Livewire 提供了 Synthesizers,它是处理不同属性类型的高级内部机制。了解更多关于 Synthesizers 的信息。

从JavaScript访问属性

由于 Livewire 属性也可以在浏览器中通过 JavaScript 访问,您可以从 AlpineJS 访问和操作它们。

Alpine 是一个轻量级的JavaScript库,Livewire 依赖它。Alpine 提供了一种在 Livewire 组件中构建轻量级交互的方式,而无需进行完整的服务器交互。

从内部来看,Livewire 的前端是基于 Alpine 构建的。每个 Livewire 组件底层实际上都是 Alpine 组件,这意味着您可以在Livewire组件内自由使用Alpine。

本页的其余部分假定您对Alpine有基本的了解。如果您对Alpine不熟悉,请查看Alpine文档

访问属性

Livewire 向 Alpine 开放了一个神奇的 $wire 对象。您可以从 Livewire 组件内部的任何 Alpine 表达式中访问 $wire 对象。

$wire 对象可以被视为您的 Livewire 组件的 JavaScript 实例。它具有与您的组件的 PHP 类相同的属性和方法,以及一些用于在模板中执行特定功能的专用方法。

例如,我们可以使用 $wire 来实时显示待办事项输入字段的字符计数:

<div>
    <input type="text" wire:model="todo">

    Todo character length: <h2 x-text="$wire.todo.length"></h2>
</div>

当用户在字段中键入时,将在页面上实时显示当前待办事项的字符长度,而且无需发送网络请求到服务器。

也可以使用更明确的 .get() 方法来完成相同的事情:

<div>
    <input type="text" wire:model="todo">

    Todo character length: <h2 x-text="$wire.get('todo').length"></h2>
</div>

操作属性

也可以使用 $wire 在 JavaScript 中操作您的 Livewire 组件属性。

例如,向 TodoList 组件添加一个 “Clear” 按钮,让允许用户仅使用 JavaScript 就可以重置输入值:

<div>
    <input type="text" wire:model="todo">

    <button x-on:click="$wire.todo = ''">Clear</button>
</div>

用户点击“Clear”后,输入值将重置为空字符串,而且无需发送网络请求到服务器。

在后续的请求中,服务器端的 $todo 值也会被更新和同步。

还可以使用更明确的 .set() 方法来在客户端设置属性。但需要注意的是,使用默认值调用 .set() 会立即触发网络请求并将与服务器同步状态:

<button x-on:click="$wire.set('todo', '')">Clear</button>

为了在不发送网络请求到服务器的情况下更新属性,您可以传递第三个 bool 参数。推迟网络请求,在后续的请求中将状态同步到服务器:

<button x-on:click="$wire.set('todo', '', false)">Clear</button>

安全性考虑

尽管 Livewire 属性非常强大,但在使用它们之前,您应该了解一些安全性问题。

简而言之,始终将公共属性视为用户输入值,就好像它们是传统路由的请求输入一样。因此,在将其持久化到数据库之前,验证和授权是至关重要的,就像在控制器中处理请求输入时所做的一样。

不要相信属性值

为了演示忽略授权和验证属性会如何在应用程序中导致安全漏洞,以下 UpdatePost 组件容易受到攻击:

<?php
namespace App\Livewire;

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

class UpdatePost extends Component{
    public $id;
    public $title;
    public $content;
    public function mount(Post $post)    {
        $this->id = $post->id;
        $this->title = $post->title;
        $this->content = $post->content;
    }

    public function update()    {
        $post = Post::findOrFail($this->id);
        $post->update([
            'title' => $this->title,
            'content' => $this->content,
        ]);
        session()->flash('message', 'Post updated successfully!');
    }

    public function render()    {
        return view('livewire.update-post');
    }
}
<form wire:submit="update">
    <input type="text" wire:model="title">
    <input type="text" wire:model="content">
    <button type="submit">Update</button>
</form>

乍一看,这个组件可能看起来完全没问题。 但是,让我们来看看攻击者如何使用该组件在您的应用程序中执行未经授权的操作。

因为我们将帖子的 id 存储为组件上的公共属性,所以可以像标题和内容属性一样在客户端上对其进行操作。

虽然我们并没有使用 wire:model="id" 绑定输入,但 恶意用户可以使用浏览器 DevTools 轻松将视图更改为以下内容:

<form wire:submit="update">
    <input type="text" wire:model="id">
    <input type="text" wire:model="title">
    <input type="text" wire:model="content">
    <button type="submit">Update</button>
</form>

现在,恶意用户随意更改模型的 ID,当提交表单并调用 update() 时,Post::findOrFail() 将返回并更新不是当前用户所有的帖子。

为了防止这种攻击,我们可以使用以下一种或两种策略:

  • 授权输入
  • 锁定属性以防止被修改

授权输入

由于 $id 可以在客户端通过类似 wire:model 的方式进行操作,就像在控制器中一样,我们可以使用 Laravel 的授权来确保当前用户可以更新帖子:

public function update()
{
    $post = Post::findOrFail($this->id);

    $this->authorize('update', $post);

    $post->update(...);
}

如果恶意用户更改了 $id 属性,添加的授权将捕获并抛出错误。

锁定属性

Livewire 还允许您“锁定”属性,以防止在客户端上修改属性。您可以使用 #[Locked] 属性来阻止从客户端修改属性:

use Livewire\Attributes\Locked;
use Livewire\Component;

class UpdatePost extends Component
{
    #[Locked]
    public $id;

    // ...
}

如果用户尝试在前端修改 $id 将引发错误。

使用了 #[Locked] 的属性不会在组件类之外的任何地方操作。

有关锁定属性的更多信息,请参阅锁定属性文档。

Eloquent模型和锁定

当将 Eloquent 模型分配给 Livewire 组件属性时,Livewire 将自动锁定该属性并确保 ID 不会更改,以保护您免受攻击:

namespace App\Livewire;

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

class UpdatePost extends Component
{
    public Post $post;
    public $title;
    public $content;

    public function mount(Post $post)
    {
        $this->post = $post;
        $this->title = $post->title;
        $this->content = $post->content;
    }

    public function update()
    {
        $this->post->update([
            'title' => $this->title,
            'content' => $this->content,
        ]);

        session()->flash('message', 'Post updated successfully!');
    }

    public function render()
    {
        return view('livewire.update-post');
    }
}

当此组件首次加载时,$post 属性将设置为用户待办事项的 Eloquent 集合;但是,数据库中的每一行只查询和加载了标题和内容字段。

当 Livewire 将此属性的 JSON 序列化 回 PHP 时,select 约束将会丢失。

为了确保 Eloquent 查询的完整性,我们建议您使用计算属性而不是普通属性。

计算属性是在您的组件中用 #[Computed] 属性标记的方法。它们可以作为动态属性访问,不作为组件状态的一部分存储,而是在运行时动态计算。

以下是使用计算属性重写上述示例的方式:

namespace App\Livewire;

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

class ShowTodos extends Component
{
    #[Computed]
    public function todos()
    {
        return Auth::user()
            ->todos()
            ->select(['title', 'content'])
            ->get();
    }

    public function render()
    {
        return view('livewire.show-todos');
    }
}

以下是如何从 Blade 视图中访问这些待办事项的方式:

<ul>
    @foreach ($this->todos as $todo)
        <li>{{ $todo }}</li>
    @endforeach
</ul>

请注意,在视图内部,您只能像这样访问计算属性:$this->todos。

您还可以在类内部访问 $todos 。例如,如果您有一个 markAllAsComplete() 操作:

namespace App\Livewire;

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

class ShowTodos extends Component
{
    #[Computed]
    public function todos()
    {
        return Auth::user()
            ->todos()
            ->select(['title', 'content'])
            ->get();
    }

    public function markAllComplete()
    {
        $this->todos->each->complete();
    }

    public function render()
    {
        return view('livewire.show-todos');
    }
}

您可能会想知道,为什么不直接在需要时直接调用 $this->todos() 方法?为什么首先要使用 #[Computed]

原因是计算属性具有性能优势,因为它们在单个请求期间首次使用后会自动缓存。这意味着您可以在组件内自由访问 $this->todos ,并确保实际方法仅在同一请求中调用一次。

有关更多信息,请访问计算属性文档。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~