主题
Alpine
AlpineJS 是一个轻量级的 JavaScript 库,可以轻松地为你的网页添加客户端交互性。它最初是为了补充像 Livewire 这样的工具而构建的,在这些工具中,以 JavaScript 为中心的实用程序有助于在应用程序周围添加交互性。
Livewire 开箱即带 Alpine,因此无需单独将其安装到项目中。
学习使用 AlpineJS 的最佳地方是 Alpine 文档。
基本的 Alpine 组件
为了为本文档的其余部分奠定基础,这里是 Alpine 组件最简单且最具信息性的示例之一。一个小的"计数器",在页面上显示一个数字,并允许用户通过点击按钮来增加该数字:
html
<!-- Declare a JavaScript object of data... -->
<div x-data="{ count: 0 }">
<!-- Render the current "count" value inside an element... -->
<h2 x-text="count"></h2>
<!-- Increment the "count" value by "1" when a click event is dispatched... -->
<button x-on:click="count++">+</button>
</div>上面的 Alpine 组件可以在应用程序中的任何 Livewire 组件内顺畅使用。Livewire 负责在 Livewire 组件更新期间维护 Alpine 的状态。本质上,你应该可以在 Livewire 内自由使用 Alpine 组件,就像你在任何其他非 Livewire 上下文中使用 Alpine 一样。
在 Livewire 中使用 Alpine
让我们探索一个更实际的示例,在 Livewire 组件内使用 Alpine 组件。
下面是一个简单的 Livewire 组件,显示数据库中帖子模型的详细信息。默认情况下,只显示帖子的标题:
html
<div>
<h1>{{ $post->title }}</h1>
<div x-data="{ expanded: false }">
<button type="button" x-on:click="expanded = ! expanded">
<span x-show="! expanded">Show post content...</span>
<span x-show="expanded">Hide post content...</span>
</button>
<div x-show="expanded">
{{ $post->content }}
</div>
</div>
</div>通过使用 Alpine,我们可以隐藏帖子的内容,直到用户按下"Show post content..."按钮。此时,Alpine 的 expanded 属性将被设置为 true,并且内容将显示在页面上,因为使用 x-show="expanded" 来让 Alpine 控制帖子内容的可见性。
这是 Alpine 大放异彩的一个例子:在不触发 Livewire 服务器往返的情况下为应用程序添加交互性。
使用 $wire 从 Alpine 控制 Livewire
作为 Livewire 开发者,你可以使用的最强大的功能之一是 $wire。$wire 对象是一个魔术对象,可供在 Livewire 内使用的所有 Alpine 组件使用。
你可以将 $wire 视为从 JavaScript 到 PHP 的网关。它允许你访问和修改 Livewire 组件属性,调用 Livewire 组件方法,以及做更多事情;所有这些都可以在 AlpineJS 内完成。
访问 Livewire 属性
这是一个在创建帖子表单中的简单"字符计数"实用程序的示例。这将在用户输入时立即向用户显示其帖子内容中包含多少个字符:
html
<form wire:submit="save">
<!-- ... -->
<input wire:model="content" type="text">
<small>
Character count: <span x-text="$wire.content.length"></span> <!-- [tl! highlight] -->
</small>
<button type="submit">Save</button>
</form>如你所见,上面示例中的 x-text 用于允许 Alpine 控制 <span> 元素的文本内容。x-text 接受其中的任何 JavaScript 表达式,并在更新任何依赖项时自动做出反应。因为我们使用 $wire.content 访问 $content 的值,所以每当 $wire.content 从 Livewire 更新时,Alpine 都会自动更新文本内容;在这种情况下是通过 wire:model="content"。
修改 Livewire 属性
这是一个在 Alpine 内使用 $wire 清除创建帖子表单的"title"字段的示例。
html
<form wire:submit="save">
<input wire:model="title" type="text">
<button type="button" x-on:click="$wire.title = ''">Clear</button> <!-- [tl! highlight] -->
<!-- ... -->
<button type="submit">Save</button>
</form>当用户填写上面的 Livewire 表单时,他们可以按"Clear",标题字段将被清除,而无需从 Livewire 发送网络请求。交互将是"即时的"。
以下是对发生这种情况的简要说明:
x-on:click告诉 Alpine 监听按钮元素上的点击- 点击时,Alpine 运行提供的 JS 表达式:
$wire.title = '' - 因为
$wire是代表 Livewire 组件的魔术对象,所以可以直接从 JavaScript 访问或修改组件中的所有属性 $wire.title = ''将 Livewire 组件中$title的值设置为空字符串- 任何 Livewire 实用程序(如
wire:model)都将立即对此更改做出反应,所有这些都无需发送服务器往返 - 在下一个 Livewire 网络请求中,
$title属性将在后端更新为空字符串
调用 Livewire 方法
Alpine 还可以通过简单地在 $wire 上直接调用任何 Livewire 方法/操作来轻松调用它们。
这是一个使用 Alpine 监听输入上的"blur"事件并触发表单保存的示例。当用户按"tab"键将焦点从当前元素移开并聚焦到页面上的下一个元素时,浏览器会调度"blur"事件:
html
<form wire:submit="save">
<input wire:model="title" type="text" x-on:blur="$wire.save()"> <!-- [tl! highlight] -->
<!-- ... -->
<button type="submit">Save</button>
</form>通常,在这种情况下,你只需使用 wire:model.blur="title",然而,出于演示目的,了解如何使用 Alpine 实现这一点是有帮助的。
传递参数
你还可以通过简单地将参数传递给 $wire 方法调用来将参数传递给 Livewire 方法。
考虑一个具有 deletePost() 方法的组件,如下所示:
php
public function deletePost($postId)
{
$post = Post::find($postId);
// Authorize user can delete...
auth()->user()->can('update', $post);
$post->delete();
}现在,你可以像这样从 Alpine 将 $postId 参数传递给 deletePost() 方法:
html
<button type="button" x-on:click="$wire.deletePost(1)">通常,像 $postId 这样的东西会在 Blade 中生成。这是一个使用 Blade 确定 Alpine 传递给 deletePost() 的 $postId 的示例:
html
@foreach ($posts as $post)
<button type="button" x-on:click="$wire.deletePost({{ $post->id }})">
Delete "{{ $post->title }}"
</button>
@endforeach如果页面上有三个帖子,上面的 Blade 模板将在浏览器中渲染为类似以下内容:
html
<button type="button" x-on:click="$wire.deletePost(1)">
Delete "The power of walking"
</button>
<button type="button" x-on:click="$wire.deletePost(2)">
Delete "How to record a song"
</button>
<button type="button" x-on:click="$wire.deletePost(3)">
Delete "Teach what you learn"
</button>如你所见,我们使用 Blade 将不同的帖子 ID 渲染到 Alpine x-on:click 表达式中。
Blade 参数"陷阱"
这是一种极其强大的技术,但在阅读 Blade 模板时可能会令人困惑。一眼看上去很难知道哪些部分是 Blade,哪些部分是 Alpine。因此,检查页面上渲染的 HTML 以确保你期望渲染的内容是准确的,这很有帮助。
这是一个经常让人困惑的示例:
假设,你的 Post 模型使用 UUID 作为索引,而不是 ID(ID 是整数,UUID 是长字符串)。
如果我们像使用 ID 那样渲染以下内容,就会出现问题:
html
<!-- Warning: this is an example of problematic code... -->
<button
type="button"
x-on:click="$wire.deletePost({{ $post->uuid }})"
>上面的 Blade 模板将在 HTML 中渲染以下内容:
html
<!-- Warning: this is an example of problematic code... -->
<button
type="button"
x-on:click="$wire.deletePost(93c7b04c-c9a4-4524-aa7d-39196011b81a)"
>注意 UUID 字符串周围缺少引号?当 Alpine 计算此表达式时,JavaScript 将抛出错误:"Uncaught SyntaxError: Invalid or unexpected token"。
要解决这个问题,我们需要在 Blade 表达式周围添加引号,如下所示:
html
<button
type="button"
x-on:click="$wire.deletePost('{{ $post->uuid }}')"
>现在上面的模板将正确渲染,一切都会按预期工作:
html
<button
type="button"
x-on:click="$wire.deletePost('93c7b04c-c9a4-4524-aa7d-39196011b81a')"
>刷新组件
你可以使用 $wire.$refresh() 轻松刷新 Livewire 组件(触发网络往返以重新渲染组件的 Blade 视图):
html
<button type="button" x-on:click="$wire.$refresh()">使用 $wire.entangle 共享状态
在大多数情况下,$wire 是你从 Alpine 与 Livewire 状态交互所需的全部内容。但是,Livewire 提供了一个额外的 $wire.entangle() 实用程序,可用于保持 Livewire 中的值与 Alpine 中的值同步。
为了演示,考虑这个下拉菜单示例,其 showDropdown 属性使用 $wire.entangle() 在 Livewire 和 Alpine 之间纠缠在一起。通过使用纠缠,我们现在能够从 Alpine 和 Livewire 控制下拉菜单的状态:
php
<?php // resources/views/components/⚡post-dropdown.blade.php
use Livewire\Component;
new class extends Component
{
public $showDropdown = false;
public function archive()
{
// ...
$this->showDropdown = false;
}
public function delete()
{
// ...
$this->showDropdown = false;
}
};
?>
<div x-data="{ open: $wire.entangle('showDropdown') }">
<button x-on:click="open = true">Show More...</button>
<ul x-show="open" x-on:click.outside="open = false">
<li><button wire:click="archive">Archive</button></li>
<li><button wire:click="delete">Delete</button></li>
</ul>
</div>用户现在可以使用 Alpine 立即切换下拉菜单,但当他们点击像"Archive"这样的 Livewire 操作时,下拉菜单将被 Livewire 告知关闭。Alpine 和 Livewire 都可以操作各自的属性,另一个会自动更新。
默认情况下,状态更新被延迟(在客户端更改,但不会立即在服务器上更改),直到下一个 Livewire 请求。如果你需要在用户点击后立即更新服务器端状态,请像这样链接 .live 修饰符:
blade
<div x-data="{ open: $wire.entangle('showDropdown').live }">
...
</div>你可能不需要 $wire.entangle
在大多数情况下,你可以通过使用 $wire 直接从 Alpine 访问 Livewire 属性来实现你想要的,而不是纠缠它们。纠缠两个属性而不是依赖一个属性,在使用频繁更改的深度嵌套对象时可能会导致可预测性和性能问题。因此,从版本 3 开始,Livewire 的文档中已经不再强调 $wire.entangle。
避免使用 @@entangle 指令
在 Livewire 版本 2 中,建议使用 Blade 的 @@entangle 指令。但在 v3 中情况已不再如此。首选 $wire.entangle(),因为它是一个更健壮的实用程序,并避免了某些删除 DOM 元素时的问题。
在 JavaScript 构建中手动打包 Alpine
默认情况下,Livewire 和 Alpine 的 JavaScript 会自动注入到每个 Livewire 页面。
这对于更简单的设置来说是理想的,但是,你可能希望将自己的 Alpine 组件、store 和插件包含到项目中。
通过自己的 JavaScript 包在页面上包含 Livewire 和 Alpine 是很简单的。
首先,你必须在布局文件中包含 @livewireScriptConfig 指令,如下所示:
blade
<html>
<head>
<!-- ... -->
@livewireStyles
@vite(['resources/js/app.js'])
</head>
<body>
{{ $slot }}
@livewireScriptConfig <!-- [tl! highlight] -->
</body>
</html>这允许 Livewire 为你的包提供应用程序正常运行所需的某些配置。
现在你可以像这样在 resources/js/app.js 文件中导入 Livewire 和 Alpine:
js
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
// Register any Alpine directives, components, or plugins here...
Livewire.start()这是在应用程序中注册名为"x-clipboard"的自定义 Alpine 指令的示例:
js
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
Alpine.directive('clipboard', (el) => {
let text = el.textContent
el.addEventListener('click', () => {
navigator.clipboard.writeText(text)
})
})
Livewire.start()现在,x-clipboard 指令将可用于 Livewire 应用程序中的所有 Alpine 组件。