Alpine

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

AlpineJS 是一个轻量级 JavaScript 库,可轻松为您的网页添加客户端交互性。它最初是为了补充 Livewire 等工具而构建的,其中更以 JavaScript 为中心的实用程序有助于在您的应用中散布交互性。

Livewire 开箱即用 Alpine,因此无需单独将其安装到您的项目中。

了解如何使用 AlpineJS 的最佳场所是 Alpine 文档

基本 Alpine 组件

为了为本文档的其余部分奠定基础,这里有一个最简单且最具参考性的 Alpine 组件示例。一个小的“计数器”,它在页面上显示一个数字,并允许用户通过单击按钮增加该数字

<!-- 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 组件,显示来自数据库的帖子模型的详细信息。默认情况下,只显示帖子的标题

<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,我们可以在用户按下“显示帖子内容...”按钮之前隐藏帖子内容。此时,Alpine 的 expanded 属性将设置为 true,并且内容将显示在页面上,因为 x-show="content" 用于让 Alpine 控制帖子内容的可见性。

这是一个 Alpine 闪耀的示例:在不触发 Livewire 服务器往返的情况下向应用程序中添加交互性。

使用 $wire 从 Alpine 控制 Livewire

作为 Livewire 开发人员,您可以使用最强大的功能之一是 $wire$wire 对象是一个神奇的对象,可供所有在 Livewire 中使用的 Alpine 组件使用。

您可以将 $wire 视为从 JavaScript 到 PHP 的网关。它允许您访问和修改 Livewire 组件属性、调用 Livewire 组件方法,以及更多操作;所有这些都可以在 AlpineJS 中进行。

访问 Livewire 属性

以下是一个简单的“字符计数”实用程序示例,用于创建帖子的表单中。这将立即向用户显示他们在键入时帖子内容中包含多少个字符

<form wire:submit="save">
<!-- ... -->
 
<input wire:model="content" type="text">
 
<small>
Character count: <span x-text="$wire.content.length"></span>
</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 清除用于创建帖子的表单的“标题”字段的示例。

<form wire:submit="save">
<input wire:model="title" type="text">
 
<button type="button" x-on:click="$wire.title = ''">Clear</button>
 
<!-- ... -->
 
<button type="submit">Save</button>
</form>

当用户填写上面的 Livewire 表单时,他们可以按“清除”,标题字段将被清除,而无需从 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 监听输入上的“模糊”事件并触发表单保存的示例。“模糊”事件是由浏览器在用户按“tab”键从当前元素移除焦点并聚焦于页面上的下一个元素时触发的

<form wire:submit="save">
<input wire:model="title" type="text" x-on:blur="$wire.save()">
 
<!-- ... -->
 
<button type="submit">Save</button>
</form>

通常,在这种情况下,你只需使用 wire:model.blur="title",但是,了解如何使用 Alpine 实现此目的有助于演示。

传递参数

你还可以通过将参数传递给 $wire 方法调用来将参数传递给 Livewire 方法。

考虑一个具有 deletePost() 方法的组件,如下所示

public function deletePost($postId)
{
$post = Post::find($postId);
 
// Authorize user can delete...
auth()->user()->can('update', $post);
 
$post->delete();
}

现在,你可以像这样从 Alpine 将 $postId 参数传递给 deletePost() 方法

<button type="button" x-on:click="$wire.deletePost(1)">

通常,会在 Blade 中生成类似 $postId 的内容。以下是如何使用 Blade 确定 Alpine 传递给 deletePost()$postId 的示例

@foreach ($posts as $post)
<button type="button" x-on:click="$wire.deletePost({{ $post->id }})">
Delete "{{ $post->title }}"
</button>
@endforeach

如果页面上有三个帖子,则上述 Blade 模板将在浏览器中呈现为类似以下内容

<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 模型不使用 ID,而是使用 UUID 作为索引(ID 是整数,UUID 是很长的字符串)。

如果我们像使用 ID 一样呈现以下内容,就会出现问题

<!-- Warning: this is an example of problematic code... -->
<button
type="button"
x-on:click="$wire.deletePost({{ $post->uuid }})"
>

上面的 Blade 模板将在你的 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 将抛出错误:“未捕获的语法错误:无效或意外的令牌”。

要解决此问题,我们需要像这样在 Blade 表达式周围添加引号

<button
type="button"
x-on:click="$wire.deletePost('{{ $post->uuid }}')"
>

现在,上面的模板将正确呈现,并且一切都会按预期工作

<button
type="button"
x-on:click="$wire.deletePost('93c7b04c-c9a4-4524-aa7d-39196011b81a')"
>

刷新组件

你可以使用 $wire.$refresh() 轻松刷新 Livewire 组件(触发网络往返以重新呈现组件的 Blade 视图)。

<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 控制下拉状态

use Livewire\Component;
 
class PostDropdown 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 立即切换下拉,但当他们单击 Livewire 操作(如“存档”)时,Livewire 将被告知关闭下拉。Alpine 和 Livewire 都可以操作各自的属性,而另一个属性将自动更新。

默认情况下,更新状态(客户端上的更改,但服务器上不会立即更新)将被延迟,直到下一次 Livewire 请求。如果你需要在用户点击时立即更新服务器端状态,请像这样链接 .live 修饰符

<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 组件、存储和插件包含到你的项目中。

通过你自己的 JavaScript 捆绑在页面上包含 Livewire 和 Alpine 非常简单。

首先,你必须在你的布局文件中包含 @livewireScriptConfig 指令,如下所示

<html>
<head>
<!-- ... -->
@livewireStyles
@vite(['resources/js/app.js'])
</head>
<body>
{{ $slot }}
 
@livewireScriptConfig
</body>
</html>

这允许 Livewire 为你的捆绑提供它为你的应用程序正常运行所需的某些配置。

现在,你可以像这样在你的 resources/js/app.js 文件中导入 Livewire 和 Alpine

import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
 
// Register any Alpine directives, components, or plugins here...
 
Livewire.start()

以下是在你的应用程序中注册一个名为“x-clipboard”的自定义 Alpine 指令的示例

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 组件。