Skip to content

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