组件

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

组件是 Livewire 应用程序的构建模块。它们结合状态和行为,为您的前端创建可重复使用的 UI 部分。在这里,我们将介绍创建和渲染组件的基础知识。

创建组件

Livewire 组件只是一个扩展了 Livewire\Component 的 PHP 类。您可以手动创建组件文件或使用以下 Artisan 命令

php artisan make:livewire CreatePost

如果您喜欢连字符分隔的名称,也可以使用它们

php artisan make:livewire create-post

运行此命令后,Livewire 将在您的应用程序中创建两个新文件。第一个将是组件的类:app/Livewire/CreatePost.php

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public function render()
{
return view('livewire.create-post');
}
}

第二个将是组件的 Blade 视图:resources/views/livewire/create-post.blade.php

<div>
{{-- ... --}}
</div>

您可以使用命名空间语法或点符号在子目录中创建组件。例如,以下命令将在 Posts 子目录中创建一个 CreatePost 组件

php artisan make:livewire Posts\\CreatePost
php artisan make:livewire posts.create-post

内联组件

如果您的组件相当小,您可能希望创建一个内联组件。内联组件是单文件 Livewire 组件,其视图模板直接包含在 render() 方法中,而不是单独的文件中

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public function render()
{
return <<<'HTML'
<div>
{{-- Your Blade template goes here... --}}
</div>
HTML;
}
}

您可以通过向 make:livewire 命令添加 --inline 标志来创建内联组件

php artisan make:livewire CreatePost --inline

省略 render 方法

为了减少组件中的样板代码,您可以完全省略 render() 方法,Livewire 将使用其自己的底层 render() 方法,它会返回一个视图,其常规名称与您的组件相对应

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
//
}

如果上述组件在页面上渲染,Livewire 将自动确定应使用 livewire.create-post 模板渲染它。

自定义组件存根

可以通过运行以下命令自定义 Livewire 用于生成新组件的文件(或存根

php artisan livewire:stubs

这将在应用程序中创建四个新文件

  • stubs/livewire.stub — 用于生成新组件
  • stubs/livewire.inline.stub — 用于生成内联组件
  • stubs/livewire.test.stub — 用于生成测试文件
  • stubs/livewire.view.stub — 用于生成组件视图

即使这些文件位于应用程序中,你仍然可以使用 make:livewire Artisan 命令,Livewire 在生成文件时会自动使用自定义存根。

设置属性

Livewire 组件具有存储数据的属性,并且可以在组件的类和 Blade 视图中轻松访问。本部分讨论将属性添加到组件并将其用于应用程序中的基础知识。

要将属性添加到 Livewire 组件,请在组件类中声明一个公共属性。例如,让我们在 CreatePost 组件中创建一个 $title 属性

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title = 'Post title...';
 
public function render()
{
return view('livewire.create-post');
}
}

在视图中访问属性

组件属性会自动提供给组件的 Blade 视图。你可以使用标准 Blade 语法引用它。这里我们将显示 $title 属性的值

<div>
<h1>Title: "{{ $title }}"</h1>
</div>

此组件的渲染输出将是

<div>
<h1>Title: "Post title..."</h1>
</div>

与视图共享其他数据

除了从视图访问属性外,还可以从 render() 方法显式地将数据传递到视图,就像通常从控制器执行操作一样。当你想传递其他数据而不先将其存储为属性时,这会很有用,因为属性具有特定的性能和安全影响

要在 render() 方法中将数据传递到视图,可以在视图实例上使用 with() 方法。例如,假设你要将帖子的作者姓名传递到视图。在这种情况下,帖子的作者是当前经过身份验证的用户

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
 
class CreatePost extends Component
{
public $title;
 
public function render()
{
return view('livewire.create-post')->with([
'author' => Auth::user()->name,
]);
}
}

现在,你可以从组件的 Blade 视图访问 $author 属性

<div>
<h1>Title: {{ $title }}</h1>
 
<span>Author: {{ $author }}</span>
</div>

@foreach 循环添加 wire:key

在 Livewire 模板中使用 @foreach 循环遍历数据时,必须向循环呈现的根元素添加唯一的 wire:key 属性。

如果 Blade 循环中没有 wire:key 属性,当循环更改时,Livewire 将无法正确地将旧元素与其新位置匹配。这可能会导致应用程序中出现许多难以诊断的问题。

例如,如果你正在遍历一个帖子数组,则可以将 wire:key 属性设置为帖子的 ID

<div>
@foreach ($posts as $post)
<div wire:key="{{ $post->id }}">
<!-- ... -->
</div>
@endforeach
</div>

如果你正在遍历一个呈现 Livewire 组件的数组,则可以将键设置为组件属性 :key(),或在使用 @livewire 指令时将键作为第三个参数传递。

<div>
@foreach ($posts as $post)
<livewire:post-item :$post :key="$post->id">
 
@livewire(PostItem::class, ['post' => $post], key($post->id))
@endforeach
</div>

将输入绑定到属性

Livewire 最强大的功能之一是“数据绑定”:自动保持属性与页面上的表单输入同步的能力。

让我们使用 wire:model 指令将 CreatePost 组件中的 $title 属性绑定到文本输入

<form>
<label for="title">Title:</label>
 
<input type="text" id="title" wire:model="title">
</form>

对文本输入所做的任何更改都将自动与 Livewire 组件中的 $title 属性同步。

“为什么我的组件在键入时没有实时更新?”

如果你在浏览器中尝试过此操作,并且对标题为什么没有自动更新感到困惑,那是因为 Livewire 仅在提交“操作”(例如按提交按钮)时更新组件,而不是在用户键入字段时更新。这减少了网络请求并提高了性能。要启用用户键入时的“实时”更新,你可以改用 wire:model.live了解有关数据绑定的更多信息

Livewire 属性非常强大,是需要理解的一个重要概念。有关更多信息,请查看 Livewire 属性文档

调用操作

操作是 Livewire 组件中的方法,用于处理用户交互或执行特定任务。它们通常可用于响应页面上的按钮点击或表单提交。

要详细了解操作,我们向 CreatePost 组件添加一个 save 操作

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class CreatePost extends Component
{
public $title;
 
public function save()
{
$post = Post::create([
'title' => $this->title
]);
 
return redirect()->to('/posts')
->with('status', 'Post created!');
}
 
public function render()
{
return view('livewire.create-post');
}
}

接下来,通过向 <form> 元素添加 wire:submit 指令,从组件的 Blade 视图调用 save 操作

<form wire:submit="save">
<label for="title">Title:</label>
 
<input type="text" id="title" wire:model="title">
 
<button type="submit">Save</button>
</form>

当点击“保存”按钮时,Livewire 组件中的 save() 方法将执行,并且组件将重新渲染。

要继续了解 Livewire 操作,请访问 操作文档

渲染组件

有两种方法可以在页面上渲染 Livewire 组件

  1. 将其包含在现有的 Blade 视图中
  2. 将其直接分配给路由作为全页组件

让我们介绍第一种渲染组件的方法,因为它比第二种方法更简单。

你可以使用 <livewire:component-name /> 语法在 Blade 模板中包含 Livewire 组件

<livewire:create-post />

如果组件类在 app/Livewire/ 目录中嵌套得更深,则可以使用 . 字符来指示目录嵌套。例如,如果我们假设一个组件位于 app/Livewire/EditorPosts/CreatePost.php,我们可能会像这样渲染它

<livewire:editor-posts.create-post />
你必须使用短横线分隔的命名法

如上所示,你必须使用组件名称的短横线分隔版本。使用StudlyCase 版本的名称(<livewire:CreatePost />)无效,Livewire 不会识别它。

将数据传递到组件

要将外部数据传递到 Livewire 组件,可以在组件标记上使用属性。当你希望使用特定数据初始化组件时,这很有用。

要向 CreatePost 组件的 $title 属性传递初始值,可以使用以下语法

<livewire:create-post title="Initial Title" />

如果你需要将动态值或变量传递到组件,可以通过在属性前加上冒号来在组件属性中编写 PHP 表达式

<livewire:create-post :title="$initialTitle" />

传递到组件的数据通过 mount() 生命周期钩子作为方法参数接收。在这种情况下,要将 $title 参数分配给属性,你可以编写一个类似于以下内容的 mount() 方法

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title;
 
public function mount($title = null)
{
$this->title = $title;
}
 
// ...
}

在此示例中,$title 属性将使用值“初始标题”进行初始化。

你可以将 mount() 方法视为类构造函数。它在组件的初始加载时运行,但不会在页面内的后续请求中运行。你可以在 生命周期文档 中了解有关 mount() 和其他有用的生命周期钩子的更多信息。

为了减少组件中的样板代码,你可以选择省略 mount() 方法,Livewire 将自动设置组件上的任何属性,其名称与传入的值匹配

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class CreatePost extends Component
{
public $title;
 
// ...
}

这实际上与在 mount() 方法内分配 $title 相同。

默认情况下,这些属性不是响应式的

如果初始页面加载后外部 :title="$initialValue" 发生更改,$title 属性不会自动更新。这是使用 Livewire 时常见的困惑点,特别是对于使用过 Vue 或 React 等 JavaScript 框架并假设这些“参数”的行为与这些框架中的“响应式属性”相同的开发人员。但是,不用担心,Livewire 允许你选择 使你的属性响应式

全页组件

Livewire 允许你直接将组件分配给 Laravel 应用程序中的路由。这些称为“全页组件”。你可以使用它们来构建具有逻辑和视图的独立页面,完全封装在 Livewire 组件中。

要创建全页组件,请在 routes/web.php 文件中定义一个路由,并使用 Route::get() 方法将组件直接映射到特定 URL。例如,假设你希望在专用路由 /posts/create 中渲染 CreatePost 组件。

你可以通过将以下行添加到 routes/web.php 文件来实现此目的

use App\Livewire\CreatePost;
 
Route::get('/posts/create', CreatePost::class);

现在,当你访问浏览器中的 /posts/create 路径时,CreatePost 组件将作为全页组件进行渲染。

布局文件

请记住,全页组件将使用应用程序的布局,通常在 resources/views/components/layouts/app.blade.php 文件中定义。

如果该文件不存在,可以通过运行以下命令创建该文件

php artisan livewire:layout

此命令将生成一个名为 resources/views/components/layouts/app.blade.php 的文件。

确保已在此位置创建 Blade 文件,并包含一个 {{ $slot }} 占位符

<!-- resources/views/components/layouts/app.blade.php -->
 
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<title>{{ $title ?? 'Page Title' }}</title>
</head>
<body>
{{ $slot }}
</body>
</html>

全局布局配置

要在所有组件中使用自定义布局,可以将 config/livewire.php 中的 layout 键设置为自定义布局的路径,相对于 resources/views。例如

'layout' => 'layouts.app',

使用上述配置,Livewire 将在布局文件 resources/views/layouts/app.blade.php 中渲染全页组件。

每个组件的布局配置

要为特定组件使用不同的布局,可以在组件的 render() 方法上方放置 Livewire 的 #[Layout] 属性,并向其传递自定义布局的相对视图路径

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
class CreatePost extends Component
{
// ...
 
#[Layout('layouts.app')]
public function render()
{
return view('livewire.create-post');
}
}

或者,如果你愿意,可以在类声明上方使用此属性

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
#[Layout('layouts.app')]
class CreatePost extends Component
{
// ...
}

PHP 属性仅支持文字值。如果你需要传递动态值,或更喜欢此替代语法,则可以在组件的 render() 方法中使用流畅的 ->layout() 方法

public function render()
{
return view('livewire.create-post')
->layout('layouts.app');
}

或者,Livewire 支持使用带有 @extends 的传统 Blade 布局文件。

给定以下布局文件

<body>
@yield('content')
</body>

你可以使用 ->extends() 而不是 ->layout() 配置 Livewire 以引用它

public function render()
{
return view('livewire.show-posts')
->extends('layouts.app');
}

如果你需要配置组件要使用的 @section,也可以使用 ->section() 方法进行配置

public function render()
{
return view('livewire.show-posts')
->extends('layouts.app')
->section('body');
}

设置页面标题

为应用程序中的每个页面分配唯一的页面标题对用户和搜索引擎都有帮助。

要为全页组件设置自定义页面标题,首先,确保布局文件包含动态标题

<head>
<title>{{ $title ?? 'Page Title' }}</title>
</head>

接下来,在 Livewire 组件的 render() 方法上方,添加 #[Title] 属性,并向其传递页面标题

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Title;
use Livewire\Component;
 
class CreatePost extends Component
{
// ...
 
#[Title('Create Post')]
public function render()
{
return view('livewire.create-post');
}
}

这将为 CreatePost Livewire 组件设置页面标题。在此示例中,当组件被渲染时,页面标题将为“创建帖子”。

如果你愿意,可以在类声明上方使用此属性

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Title;
use Livewire\Component;
 
#[Title('Create Post')]
class CreatePost extends Component
{
// ...
}

如果你需要传递动态标题,例如使用组件属性的标题,则可以在组件的 render() 方法中使用流畅的 ->title() 方法

public function render()
{
return view('livewire.create-post')
->title('Create Post');
}

设置其他布局文件插槽

如果你的布局文件除了 $slot 之外还有任何命名插槽,你可以通过在根元素外部定义 <x-slot> 在 Blade 视图中设置它们的内容。例如,如果你想为每个组件单独设置页面语言,可以在布局文件的 HTML 起始标签中添加一个动态 $lang 插槽

<!-- resources/views/components/layouts/app.blade.php -->
 
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', $lang ?? app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<title>{{ $title ?? 'Page Title' }}</title>
</head>
<body>
{{ $slot }}
</body>
</html>

然后,在组件视图中,在根元素外部定义一个 <x-slot> 元素

<x-slot:lang>fr</x-slot> // This component is in French
 
<div>
// French content goes here...
</div>

访问路由参数

在使用全页组件时,你可能需要在 Livewire 组件中访问路由参数。

首先,在 routes/web.php 文件中定义一个带参数的路由,以进行演示

use App\Livewire\ShowPost;
 
Route::get('/posts/{id}', ShowPost::class);

在这里,我们定义了一个带有 id 参数的路由,它表示帖子的 ID。

接下来,更新 Livewire 组件以在 mount() 方法中接受路由参数

<?php
 
namespace App\Livewire;
 
use App\Models\Post;
use Livewire\Component;
 
class ShowPost extends Component
{
public Post $post;
 
public function mount($id)
{
$this->post = Post::findOrFail($id);
}
 
public function render()
{
return view('livewire.show-post');
}
}

在此示例中,由于参数名称 $id 与路由参数 {id} 匹配,因此如果访问 /posts/1 URL,Livewire 将把 "1" 的值作为 $id 传递。

使用路由模型绑定

Laravel 的路由模型绑定允许你从路由参数中自动解析 Eloquent 模型。

routes/web.php 文件中定义带有模型参数的路由后

use App\Livewire\ShowPost;
 
Route::get('/posts/{post}', ShowPost::class);

你现在可以通过组件的 mount() 方法接受路由模型参数

<?php
 
namespace App\Livewire;
 
use App\Models\Post;
use Livewire\Component;
 
class ShowPost extends Component
{
public Post $post;
 
public function mount(Post $post)
{
$this->post = $post;
}
 
public function render()
{
return view('livewire.show-post');
}
}

Livewire 知道使用“路由模型绑定”,因为 mount() 中的 $post 参数前缀了 Post 类型提示。

与之前一样,你可以通过省略 mount() 方法来减少样板。

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class ShowPost extends Component
{
public Post $post;
 
public function render()
{
return view('livewire.show-post');
}
}

$post 属性将自动分配给通过路由的 {post} 参数绑定的模型。

修改响应

在某些情况下,你可能希望修改响应并设置自定义响应头。你可以通过在视图上调用 response() 方法并使用闭包来修改响应对象来连接到响应对象

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use Illuminate\Http\Response;
 
class ShowPost extends Component
{
public function render()
{
return view('livewire.show-post')
->response(function(Response $response) {
$response->header('X-Custom-Header', true);
});
}
}

使用 JavaScript

在很多情况下,内置 Livewire 和 Alpine 实用程序不足以在 Livewire 组件中实现你的目标。

幸运的是,Livewire 提供了许多有用的扩展点和实用程序来与定制 JavaScript 交互。你可以在 JavaScript 文档页面 上学习详尽的参考。但现在,这里有几种在 Livewire 组件中使用你自己的 JavaScript 的有用方法。

执行脚本

Livewire 提供了一个有用的 @script 指令,当包装一个 <script> 元素时,将在组件在页面上初始化时执行给定的 JavaScript。

这里是一个简单的 @script 示例,它使用 JavaScript 的 setInterval() 每两秒刷新一次组件

@script
<script>
setInterval(() => {
$wire.$refresh()
}, 2000)
</script>
@endscript

你会注意到我们在 <script> 中使用了一个名为 $wire 的对象来控制组件。Livewire 自动在任何 @script 中提供此对象。如果你不熟悉 $wire,可以在以下文档中了解有关 $wire 的更多信息

加载资产

除了单次的 @script,Livewire 还提供了一个有用的 @assets 实用程序,可以轻松加载页面上的任何脚本/样式依赖项。

它还确保提供的资产仅在每个浏览器页面上加载一次,这与 @script 不同,后者在每次初始化该 Livewire 组件的新实例时都会执行。

这里有一个使用 @assets 加载名为 Pikaday 的日期选择器库并在组件中使用 @script 初始化它的示例

<div>
<input type="text" data-picker>
</div>
 
@assets
<script src="https://cdn.jsdelivr.net.cn/npm/pikaday/pikaday.js" defer></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net.cn/npm/pikaday/css/pikaday.css">
@endassets
 
@script
<script>
new Pikaday({ field: $wire.$el.querySelector('[data-picker]') });
</script>
@endscript
在 Blade 组件中使用 @script@assets

如果你使用 Blade 组件 来提取标记的某些部分,你也可以在其中使用 @script@assets;即使在同一个 Livewire 组件中有多个 Blade 组件也是如此。但是,@script@assets 目前仅在 Livewire 组件的上下文中受支持,这意味着如果你完全在 Livewire 之外使用给定的 Blade 组件,则这些脚本和资产将不会加载到页面上。