Skip to content

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 分发和监听事件