主题
Alpine
AlpineJS 是一个轻量级的 JavaScript 库,可以轻松地为你的网页添加客户端交互性。它最初是为了补充 Livewire 等工具而构建的,在需要更多 JavaScript 为中心的工具来为应用程序添加交互性时非常有用。
Livewire 开箱即用地附带了 Alpine,因此无需单独将其安装到你的项目中。
学习使用 AlpineJS 的最佳地方是 Alpine 文档。
一个基本的 Alpine 组件
为本文档的其余部分奠定基础,这是一个最简单和最有信息量的 Alpine 组件示例。一个小"计数器",在页面上显示一个数字,并允许用户通过点击按钮来增加该数字:
html
<!-- 声明一个 JavaScript 数据对象... -->
<div x-data="{ count: 0 }">
<!-- 在元素内渲染当前的 "count" 值... -->
<h2 x-text="count"></h2>
<!-- 当分发点击事件时,将 "count" 值增加 "1"... -->
<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">显示帖子内容...</span>
<span x-show="expanded">隐藏帖子内容...</span>
</button>
<div x-show="expanded">
{{ $post->content }}
</div>
</div>
</div>通过使用 Alpine,我们可以隐藏帖子的内容,直到用户按下"显示帖子内容..."按钮。此时,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>
字符数: <span x-text="$wire.content.length"></span>
</small>
<button type="submit">保存</button>
</form>如你所见,上面示例中的 x-text 被用来允许 Alpine 控制 <span> 元素的文本内容。x-text 接受其中的任何 JavaScript 表达式,并在任何依赖项更新时自动响应。因为我们使用 $wire.content 来访问 $content 的值,Alpine 将在每次 $wire.content 从 Livewire 更新时自动更新文本内容;在这种情况下是通过 wire:model="content"。
修改 Livewire 属性
这是一个在 Alpine 中使用 $wire 来清除创建帖子表单的"标题"字段的示例。
html
<form wire:submit="save">
<input wire:model="title" type="text">
<button type="button" x-on:click="$wire.title = ''">清除</button>
<!-- ... -->
<button type="submit">保存</button>
</form>当用户填写上述 Livewire 表单时,他们可以按"清除",标题字段将被清除而不发送来自 Livewire 的网络请求。交互将是"即时"的。
以下是发生这种情况的简要说明:
x-on:click告诉 Alpine 监听按钮元素上的点击- 当点击时,Alpine 运行提供的 JS 表达式:
$wire.title = '' - 因为
$wire是代表 Livewire 组件的魔术对象,你组件中的所有属性都可以直接从 JavaScript 访问或修改 $wire.title = ''将 Livewire 组件中$title的值设置为空字符串- 任何像
wire:model这样的 Livewire 工具将立即响应此更改,所有这些都不需要发送服务器往返 - 在下一个 Livewire 网络请求中,
$title属性将在后端更新为空字符串
调用 Livewire 方法
Alpine 还可以通过直接在 $wire 上调用它们来轻松调用任何 Livewire 方法/操作。
这是一个使用 Alpine 监听输入上的"blur"事件并触发表单保存的示例。"blur"事件在用户按下"tab"从当前元素移除焦点并聚焦到页面上的下一个元素时由浏览器分发:
html
<form wire:submit="save">
<input wire:model="title" type="text" x-on:blur="$wire.save()">
<!-- ... -->
<button type="submit">保存</button>
</form>通常,你会在这种情况下只使用 wire:model.blur="title",然而,这对于演示如何使用 Alpine 实现这一点是有帮助的。
传递参数
你也可以通过简单地将参数传递给 $wire 方法调用来向 Livewire 方法传递参数。
考虑一个带有 deletePost() 方法的组件,如下所示:
php
public function deletePost($postId)
{
$post = Post::find($postId);
// 授权用户可以删除...
auth()->user()->can('update', $post);
$post->delete();
}现在,你可以像这样从 Alpine 向 deletePost() 方法传递 $postId 参数:
html
<button type="button" x-on:click="$wire.deletePost(1)">一般来说,像 $postId 这样的东西会在 Blade 中生成。这是一个使用 Blade 确定 Alpine 传递给 deletePost() 的 $postId 的示例:
html
@foreach ($posts as $post)
<button type="button" wire:key="{{ $post->id }}" x-on:click="$wire.deletePost({{ $post->id }})">
删除 "{{ $post->title }}"
</button>
@endforeach如果页面上有三篇帖子,上面的 Blade 模板将在浏览器中渲染成类似以下的内容:
html
<button type="button" x-on:click="$wire.deletePost(1)">
删除 "行走的力量"
</button>
<button type="button" x-on:click="$wire.deletePost(2)">
删除 "如何录制一首歌"
</button>
<button type="button" x-on:click="$wire.deletePost(3)">
删除 "教你学到的东西"
</button>如你所见,我们使用 Blade 将不同的帖子 ID 渲染到 Alpine x-on:click 表达式中。
Blade 参数"陷阱"
这是一种非常强大的技术,但在阅读 Blade 模板时可能会令人困惑。乍一看,很难知道哪些部分是 Blade,哪些部分是 Alpine。因此,检查页面上渲染的 HTML 以确保你期望渲染的内容是准确的是很有帮助的。
这是一个经常让人困惑的例子:
假设你的 Post 模型使用 UUID 作为索引而不是 ID(ID 是整数,UUID 是长字符串)。
如果我们像处理 ID 一样渲染以下内容,将会出现问题:
html
<!-- 警告:这是有问题代码的示例... -->
<button
type="button"
x-on:click="$wire.deletePost({{ $post->uuid }})"
>上面的 Blade 模板将在你的 HTML 中渲染以下内容:
html
<!-- 警告:这是有问题代码的示例... -->
<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 属性,而不是使用 $wire.entangle()。Entangling 创建重复状态,可能导致可预测性和性能问题。这个 API 是为了向后兼容而维护的,但不鼓励在新代码中使用。
不要使用 @@entangle Blade 指令 - 它已被弃用,并在删除 DOM 元素时会导致问题。
对于需要在 Alpine 和 Livewire 之间进行双向状态同步的罕见情况,你可以使用 $wire.entangle():
blade
<div x-data="{ open: $wire.entangle('showDropdown') }">
<button x-on:click="open = true">显示更多...</button>
<ul x-show="open">
<li><button wire:click="archive">归档</button></li>
</ul>
</div>默认情况下,更改会延迟到下一个 Livewire 请求。使用 .live 立即同步:
blade
<div x-data="{ open: $wire.entangle('showDropdown').live }">使用 @js 指令
如果你需要直接在 Alpine 中输出 PHP 数据以供使用,你可以使用 @js 指令。
blade
<div x-data="{ posts: @js($posts) }">
...
</div>在你的 JavaScript 构建中手动打包 Alpine
默认情况下,Livewire 和 Alpine 的 JavaScript 会自动注入到每个 Livewire 页面。
这对于更简单的设置来说是理想的,然而,你可能想要在你的项目中包含你自己的 Alpine 组件、存储和插件。
在页面上通过你自己的 JavaScript 包包含 Livewire 和 Alpine 很简单。
首先,你必须在布局文件中包含 @livewireScriptConfig 指令,如下所示:
blade
<html>
<head>
<!-- ... -->
@livewireStyles
@vite(['resources/js/app.js'])
</head>
<body>
{{ $slot }}
@livewireScriptConfig
</body>
</html>这允许 Livewire 为你的包提供应用程序正常运行所需的某些配置。
现在你可以在你的 resources/js/app.js 文件中像这样导入 Livewire 和 Alpine:
js
import { Livewire, Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
// 在这里注册任何 Alpine 指令、组件或插件...
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 组件中可用。
另请参阅
- 属性 — 使用 $wire 从 Alpine 访问 Livewire 属性
- 操作 — 从 Alpine 调用 Livewire 操作
- JavaScript — 在组件中执行自定义 JavaScript
- 事件 — 使用 Alpine 分发和监听事件