Skip to content
全部文档

JavaScript

在 Livewire 组件中使用 JavaScript

Livewire 和 Alpine 提供了大量工具,可直接在 HTML 中构建动态组件;不过有时跳出 HTML、为组件执行普通 JavaScript 会更有帮助。

WARNING

基于类的组件需要 @@script 指令

本页示例使用裸 <script> 标签,适用于单文件多文件组件。若你使用基于类的组件(Blade 视图与 PHP 类分属不同文件),则必须用 @@script 指令包裹 script 标签:

blade
@@script
<script>
    // Your JavaScript here...
</script>
@@endscript

这会告诉 Livewire 为基于类的组件正确处理执行时机。

执行脚本

你可以在组件模板中直接添加 <script> 标签,以便在组件加载时执行 JavaScript。

由于这些脚本由 Livewire 处理,它们会在恰当时机执行——页面已加载之后、Livewire 组件渲染之前。这意味着你不再需要把脚本包在 document.addEventListener('...') 里才能正确加载。

这也意味着,懒加载或条件加载的 Livewire 组件在页面初始化之后仍能执行 JavaScript。

blade
<div>
    ...
</div>

<script>
    // This Javascript will get executed every time this component is loaded onto the page...
</script>

下面是一个更完整的示例:你可以像这样注册在 Livewire 组件中使用的 JavaScript 动作。

blade
<div>
    <button wire:click="$js.increment">+</button>
</div>

<script>
    this.$js.increment = () => {
        console.log('increment')
    }
</script>

要了解更多关于 JavaScript 动作的内容,请查阅 actions 文档

脚本何时运行

组件的脚本会在其标记已进入 DOM、但 Alpine 尚未初始化它时运行。Livewire 会暂停组件树的初始化,直到脚本执行完毕,因此脚本注册的任何内容——Alpine.data() 提供者、$js 动作、自定义指令——都能保证在组件标记中的任何表达式求值之前已经存在:

blade
<div x-data="audioUploader">
    <span x-text="message"></span>
</div>

<script>
    Alpine.data('audioUploader', () => ({
        message: 'Loaded',
    }))
</script>

这一点在首次页面加载以及组件进入页面的其他所有方式下都成立:懒加载、父组件更新时的动态挂载、island,以及 wire:navigate

这一时机有几点值得了解的后果:

  • `$wire.$el` 和普通 DOM 查询可用——脚本运行时标记已存在。
  • 由初始化标记产生的内容——`$refs`、Alpine 组件状态——此时尚不存在。请从 Alpine 组件的 `init()` 钩子中获取,或在 `await $wire.$nextTick()` 之后再取。
  • 脚本加载期间,组件标记可见但尚不可交互。这个窗口通常察觉不到;但对较重的模块,你可以给在脚本就绪前不应出现的元素加上 [`x-cloak`](https://alpinejs.dev/directives/cloak)——在组件初始化之前它会被遵守。
  • 若脚本加载失败或抛错,Livewire 会记录错误并仍初始化组件,以便页面继续工作。

在脚本中使用 $wire

在组件内添加 <script> 标签时,你会自动获得该 Livewire 组件的 $wire 对象。

下面示例用简单的 setInterval 每 2 秒刷新一次组件(你完全可以用 wire:poll 做到,但这是演示要点的简单方式):

blade
<script>
    setInterval(() => {
        $wire.$refresh()
    }, 2000)
</script>

$wire 对象

$wire 对象是你与 Livewire 组件交互的 JavaScript 接口。它提供对组件属性、方法的访问,以及与服务器交互的工具。

在组件脚本中,你可以直接使用 $wire。以下是你最常用的方法:

js
// Access and modify properties
$wire.count
$wire.count = 5
$wire.$set('count', 5)

// Call component methods
$wire.save()
$wire.delete(postId)

// Refresh the component
$wire.$refresh()

// Dispatch events
$wire.$dispatch('post-created', { postId: 2 })

// Listen for events
$wire.$on('post-created', (event) => {
    console.log(event.postId)
})

// Access the root element
$wire.$el.querySelector('.modal')

TIP

完整的 $wire 参考

关于所有 $wire 方法与属性的完整列表,请参阅本页底部的 $wire 参考

加载资源

组件 <script> 标签适合在每次 Livewire 组件加载时执行少量 JavaScript;不过有时你可能希望连同组件一起,把整段脚本和样式资源加载到页面上。

下面示例用 @assets 加载名为 Pikaday 的日期选择库,并在组件内初始化:

blade
<div>
    <input type="text" data-picker>
</div>

@assets
<script src="https://cdn.jsdelivr.net/npm/pikaday/pikaday.js" defer></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css">
@endassets

<script>
    new Pikaday({ field: $wire.$el.querySelector('[data-picker]') });
</script>

该组件加载时,Livewire 会确保在执行脚本之前,页面上的所有 @assets 都已加载。此外,无论该组件有多少实例,提供的 @assets 在每个页面只会加载一次;与之不同,组件脚本会对页面上的每个组件实例都执行。

拦截器

可在三个层级拦截 Livewire 请求:action(最细粒度)、message(按组件)、以及 request(HTTP 层)。

js
// Action interceptors - fire for each action call
$wire.intercept(callback)                     // All actions on this component
$wire.intercept('save', callback)             // Only 'save' action
Livewire.interceptAction(callback)            // Global (all components)

// Message interceptors - fire for each component message
$wire.interceptMessage(callback)              // Messages from this component
$wire.interceptMessage('save', callback)      // Only when message contains 'save'
Livewire.interceptMessage(callback)           // Global (all components)

// Request interceptors - fire for each HTTP request
$wire.interceptRequest(callback)              // Requests involving this component
$wire.interceptRequest('save', callback)      // Only when request contains 'save'
Livewire.interceptRequest(callback)           // Global (all requests)

所有拦截器都会返回一个取消订阅函数:

js
let unsubscribe = $wire.intercept(callback)
unsubscribe() // Remove the interceptor

Action 拦截器

Action 拦截器粒度最细。它们会在组件的每次方法调用时触发。

js
$wire.intercept(({ action, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => {
    // action.name        - Method name ('save', '$refresh', etc.)
    // action.params      - Method parameters
    // action.component   - Component instance
    // action.cancel()    - Cancel this action

    onSend(({ call }) => {
        // call: { method, params, metadata }
    })

    onCancel(() => {})

    onSuccess((result) => {
        // result: Return value from PHP method
    })

    onError(({ response, body, preventDefault }) => {
        preventDefault() // Prevent error modal
    })

    onFailure(({ error }) => {
        // error: Network error
    })

    onFinish(() => {
        // Runs after DOM morph completes (or on error/cancel)
    })
})

Message 拦截器

Message 拦截器会在每次组件更新时触发。一条 message 包含一个或多个 action。

js
$wire.interceptMessage(({ message, cancel, onSend, onCancel, onSuccess, onSkipped, onError, onFailure, onStream, onFinish }) => {
    // message.component  - Component instance
    // message.actions    - Set of actions in this message
    // message.isSkipped() - True if the server skipped this message
    // cancel()           - Cancel this message

    onSend(({ payload }) => {
        // payload: { snapshot, updates, calls }
    })

    onCancel(() => {})

    onSuccess(({ payload, onSync, onEffect, onMorph, onMorphed, onRender }) => {
        // payload: { snapshot, effects }

        onSync(() => {})          // After state synced
        onEffect(() => {})        // After effects processed
        onMorph(async () => {})   // Advanced: add awaited DOM morph work
        onMorphed(() => {})       // After all DOM morph work completes
        onRender(() => {})        // In the next animation frame
    })

    onSkipped(() => {
        // Server intentionally skipped this message (e.g. an unchanged
        // reactive child). No payload, no morph, no render — but action
        // promises still resolve. Use for telemetry or dev tools.
    })

    onError(({ response, body, preventDefault }) => {
        preventDefault() // Prevent error modal
    })

    onFailure(({ error }) => {})

    onStream(async ({ json }) => {
        // json: Parsed stream chunk
        // Async work is awaited before the next chunk is processed
    })

    onFinish(() => {
        // Runs after DOM morph completes (or on error/cancel/skip)
    })
})

onMorph 是一个高级钩子,用于提交 Livewire 必须等待的异步 DOM 工作。大多数需要访问更新后 DOM 的代码应使用 onMorphed:它在响应中的组件、island 和 slot morph 全部完成后运行,但早于 action promise 兑现以及 onFinish 执行。

时机

成功请求时的钩子执行顺序:

  1. `onSuccess` — 收到服务器响应后立即执行
  2. `onSync` — 状态合并之后
  3. `onEffect` — effects 处理之后
  4. `onMorph` — 在被 await 的 DOM morph 阶段期间
  5. `onMorphed` — 所有 DOM morph 工作完成之后
  6. `onFinish` — 在 `onMorphed` 之后
  7. `onRender` — 在下一个 `requestAnimationFrame` 中

对于被跳过的 message(例如未变更的响应式子组件),会触发 onSkipped 而非 onSuccess,然后是 onFinish。由于没有内容可应用,morph/render 钩子都不会触发。

Action 的 promise(.then())与 onFinish 同时兑现(morph 之后,或在跳过时立即兑现)。

Request 拦截器

Request 拦截器会在每次 HTTP 请求时触发。一个 request 可能包含来自多个组件的 message。

js
$wire.interceptRequest(({ request, onSend, onCancel, onSuccess, onError, onFailure, onResponse, onParsed, onStream, onRedirect, onDump, onFinish }) => {
    // request.messages   - Set of messages in this request
    // request.cancel()   - Cancel this request

    onSend(({ responsePromise }) => {})

    onCancel(() => {})

    onResponse(({ response }) => {
        // response: Fetch Response (before body read)
    })

    onParsed(({ response, body }) => {
        // body: Response body as string
    })

    onSuccess(({ response, body, json }) => {})

    onError(({ response, body, preventDefault }) => {
        preventDefault() // Prevent error modal
    })

    onFailure(({ error }) => {})

    onStream(({ response }) => {})

    onRedirect(({ url, preventDefault }) => {
        preventDefault() // Prevent redirect
    })

    onDump(({ html, preventDefault }) => {
        preventDefault() // Prevent dump modal
    })

    onFinish(() => {})
})

示例

组件的加载状态:

blade
<script>
    $wire.intercept(({ onSend, onFinish }) => {
        onSend(() => $wire.$el.classList.add('opacity-50'))
        onFinish(() => $wire.$el.classList.remove('opacity-50'))
    })
</script>

删除前确认:

blade
<script>
    $wire.intercept('delete', ({ action }) => {
        if (!confirm('Are you sure?')) {
            action.cancel()
        }
    })
</script>

全局会话过期处理:

js
Livewire.interceptRequest(({ onError }) => {
    onError(({ response, preventDefault }) => {
        if (response.status === 419) {
            preventDefault()
            if (confirm('Session expired. Refresh?')) {
                window.location.reload()
            }
        }
    })
})

针对特定 action 的成功通知:

blade
<script>
    $wire.intercept('save', ({ onSuccess, onError }) => {
        onSuccess(() => showToast('Saved!'))
        onError(() => showToast('Failed to save', 'error'))
    })
</script>

全局 Livewire 事件

Livewire 会派发两个有用的浏览器事件,便于你从外部脚本注册任意自定义扩展点:

html
<script>
    document.addEventListener('livewire:init', () => {
        // Runs after Livewire is loaded but before it's initialized
        // on the page...
    })

    document.addEventListener('livewire:initialized', () => {
        // Runs immediately after Livewire has finished initializing
        // on the page...
    })
</script>

INFO

通常建议在 livewire:init 中注册任意自定义指令生命周期钩子,以便在 Livewire 开始初始化页面前它们就已可用。

Livewire 全局对象

Livewire 的全局对象是从外部脚本与 Livewire 交互的最佳起点。

你可以在客户端代码的任意位置,通过 window 访问全局 Livewire JavaScript 对象。

livewire:init 事件监听器中使用 window.Livewire 通常很有帮助

访问组件

你可以使用以下方法访问当前页面已加载的特定 Livewire 组件:

js
// Retrieve the $wire object for the first component on the page...
let component = Livewire.first()

// Retrieve a given component's `$wire` object by its ID...
let component = Livewire.find(id)

// Retrieve an array of component `$wire` objects by name...
let components = Livewire.getByName(name)

// Retrieve $wire objects for every component on the page...
let components = Livewire.all()

INFO

这些方法各自返回一个 $wire 对象,表示该组件在 Livewire 中的状态。



你可以在 $wire 文档中了解更多关于这些对象的信息。

与事件交互

除了在 PHP 中从各个组件派发和监听事件外,全局 Livewire 对象还允许你在应用的任意位置与 Livewire 的事件系统交互:

js
// Dispatch an event to any Livewire components listening...
Livewire.dispatch('post-created', { postId: 2 })

// Dispatch an event to a given Livewire component by name...
Livewire.dispatchTo('dashboard', 'post-created', { postId: 2 })

// Listen for events dispatched from Livewire components...
Livewire.on('post-created', ({ postId }) => {
    // ...
})

在某些场景下,你可能需要注销全局 Livewire 事件。例如,在使用 Alpine 组件和 wire:navigate 时,由于在页面间导航时会调用 init,可能注册多个监听器。为此,请使用 Alpine 会自动调用的 destroy 函数:在该函数中遍历所有监听器并注销它们,以避免不必要的累积。

js
Alpine.data('MyComponent', () => ({
    listeners: [],
    init() {
        this.listeners.push(
            Livewire.on('post-created', (options) => {
                // Do something...
            })
        );
    },
    destroy() {
        this.listeners.forEach((listener) => {
            listener();
        });
    }
}));

使用生命周期钩子

Livewire 允许你使用 Livewire.hook() 挂接到其全局生命周期的各个部分:

js
// Register a callback to execute on a given internal Livewire hook...
Livewire.hook('component.init', ({ component, cleanup }) => {
    // ...
})

关于 Livewire JavaScript 钩子的更多信息可在下方找到

注册自定义指令

Livewire 允许你使用 Livewire.directive() 注册自定义指令。

下面是一个自定义 wire:confirm 指令的示例:它用 JavaScript 的 confirm() 对话框,在动作发往服务器之前确认或取消:

html
<button wire:confirm="Are you sure?" wire:click="delete">Delete post</button>

下面是用 Livewire.directive() 实现 wire:confirm 的代码:

js
Livewire.directive('confirm', ({ el, directive, component, cleanup }) => {
    let content =  directive.expression

    // The "directive" object gives you access to the parsed directive.
    // For example, here are its values for: wire:click.prevent="deletePost(1)"
    //
    // directive.raw = wire:click.prevent
    // directive.value = "click"
    // directive.modifiers = ['prevent']
    // directive.expression = "deletePost(1)"

    let onClick = e => {
        if (! confirm(content)) {
            e.preventDefault()
            e.stopImmediatePropagation()
        }
    }

    el.addEventListener('click', onClick, { capture: true })

    // Register any cleanup code inside `cleanup()` in the case
    // where a Livewire component is removed from the DOM while
    // the page is still active.
    cleanup(() => {
        el.removeEventListener('click', onClick)
    })
})

JavaScript 钩子

面向高级用户,Livewire 暴露了其内部的客户端「hook」系统。你可以使用下列钩子扩展 Livewire 的功能,或获取关于 Livewire 应用的更多信息。

组件初始化

每当 Livewire 发现一个新组件——无论是在首次页面加载还是之后——都会触发 component.init 事件。你可以挂接到 component.init,以拦截或初始化与该新组件相关的任何内容:

js
Livewire.hook('component.init', ({ component, cleanup }) => {
    //
})

对于依赖 Livewire 初始 effects 的高级集成——例如事件监听器、脚本或服务器派发的 JavaScript——请使用 component.initialized。它在这些 effects 处理完毕之后运行,但早于 Livewire 继续初始化组件的后代元素:

js
Livewire.hook('component.initialized', ({ component }) => {
    //
})

更多信息请参阅 component 对象文档

DOM 元素初始化

除了在新组件初始化时触发事件外,Livewire 还会为给定 Livewire 组件内的每个 DOM 元素触发事件。

这可用于在应用中提供自定义的 Livewire HTML 属性:

js
Livewire.hook('element.init', ({ component, el }) => {
    //
})

DOM Morph 钩子

在 DOM morph 阶段——发生在 Livewire 完成一次网络往返之后——Livewire 会对每个被变更的元素触发一系列事件。

js
Livewire.hook('morph.updating',  ({ el, component, toEl, skip, childrenOnly }) => {
	//
})

Livewire.hook('morph.updated', ({ el, component }) => {
	//
})

Livewire.hook('morph.removing', ({ el, component, skip }) => {
	//
})

Livewire.hook('morph.removed', ({ el, component }) => {
	//
})

Livewire.hook('morph.adding',  ({ el, component }) => {
	//
})

Livewire.hook('morph.added',  ({ el }) => {
	//
})

除了按元素触发的事件外,每个 Livewire 组件还会触发 morphmorphed 事件:

js
Livewire.hook('morph',  ({ el, component }) => {
	// Runs just before the child elements in `component` are morphed (exluding partial morphing)
})

Livewire.hook('morphed',  ({ el, component }) => {
    // Runs after all child elements in `component` are morphed (excluding partial morphing)
})

服务端 JavaScript 求值

除了直接在组件中执行 JavaScript,你还可以使用 js() 方法从服务端 PHP 代码求值 JavaScript 表达式。

这通常适用于在服务端动作执行后,再做某种客户端后续操作。

例如,下面的 post.create 组件会在文章保存到数据库后,触发客户端 alert 对话框:

php
<?php // resources/views/components/post/⚡create.blade.php

use Livewire\Component;

new class extends Component {
    public $title = '';

    public function save()
    {
        // Save post to database...

        $this->js("alert('Post saved!')");
    }
};

JavaScript 表达式 alert('Post saved!') 会在服务端将文章保存到数据库、且响应的 DOM morph 完成之后,在客户端执行。

你可以在表达式中访问当前组件的 $wire 对象:

php
$this->js('$wire.$refresh()');
$this->js('$wire.$dispatch("post-created", { id: ' . $post->id . ' })');

常见模式

以下是在实际应用中将 JavaScript 与 Livewire 结合使用的一些常见模式。

集成第三方库

许多 JavaScript 库需要在元素加入页面时初始化。使用组件脚本在组件加载时初始化库:

blade
<div>
    <div id="map" style="height: 400px;"></div>
</div>

@assets
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_KEY"></script>
@endassets

<script>
    new google.maps.Map($wire.$el.querySelector('#map'), {
        center: { lat: {{ $latitude }}, lng: {{ $longitude }} },
        zoom: 12
    });
</script>

与 localStorage 同步

你可以使用 $watch 将组件状态与 localStorage 同步:

blade
<script>
    // Load from localStorage on init
    if (localStorage.getItem('draft')) {
        $wire.content = localStorage.getItem('draft');
    }

    // Save to localStorage when it changes
    $wire.$watch('content', (value) => {
        localStorage.setItem('draft', value);
    });
</script>

使用 @js 指令

若需要直接输出供 JavaScript 使用的 PHP 数据,可以使用 @js 指令。

blade
<script>
    let posts = @js($posts)

    // "posts" will now be a JavaScript array of post data from PHP.
</script>

最佳实践

组件脚本 vs 全局脚本

在以下情况使用组件脚本:

  • JavaScript 专属于该组件的功能
  • 你需要访问 `$wire` 或组件特定数据
  • 代码应在每次组件加载时运行

在以下情况使用全局脚本:

  • 注册自定义指令或钩子
  • 设置全局事件监听器
  • 初始化应用级 JavaScript

避免内存泄漏

在组件脚本中添加事件监听器时,Livewire 会在组件移除时自动清理它们。不过,若你使用全局拦截器或钩子,请确保在适当时机进行清理:

js
// Component-level - automatically cleaned up ✓
$wire.intercept(({ onSend }) => {
    onSend(() => console.log('Sending...'));
});

// Global-level - lives for the entire page lifecycle
Livewire.interceptMessage(({ onSend }) => {
    onSend(() => console.log('Sending...'));
});

调试技巧

从浏览器控制台访问组件:

js
// Get first component on page
let $wire = Livewire.first()

// Inspect component state
console.log($wire.count)

// Call methods
$wire.increment()

监控所有请求:

js
Livewire.interceptRequest(({ onSend }) => {
    onSend(() => {
        console.log('Request sent:', Date.now());
    });
});

查看组件快照:

js
let component = Livewire.first().__instance()
console.log(component.snapshot)

性能考量

  • 对不应被 Livewire DOM morph 触及的元素使用 `wire:ignore`
  • 用 `wire:model.debounce` 或 JavaScript 防抖来节流昂贵操作
  • 对非立即可见的组件使用懒加载(`lazy` 参数)
  • 对需要独立更新的隔离区域,考虑使用 island

另见

  • 样式为组件添加作用域 CSS
  • Alpine用 Alpine 做客户端交互
  • 操作在组件中创建 JavaScript 动作
  • 属性用 $wire 从 JavaScript 访问属性
  • 事件在 JavaScript 中派发与监听事件

参考

扩展 Livewire 的 JavaScript 系统时,理解你可能遇到的不同对象很重要。

以下是 Livewire 各相关内部属性的详尽参考。

提醒一下,普通 Livewire 用户可能永远不会与这些对象交互。它们大多供 Livewire 内部系统或高级用户使用。

$wire 对象

以下面这个通用的 Counter 组件为例:

php
<?php

namespace App\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public $count = 1;

    public function increment()
    {
        $this->count++;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

Livewire 以常被称为 $wire 的对象形式,暴露服务端组件的 JavaScript 表示:

js
let $wire = {
    // All component public properties are directly accessible on $wire...
    count: 0,

    // All public methods are exposed and callable on $wire...
    increment() { ... },

    // Access the `$wire` object of the parent component if one exists...
    $parent,

    // Access the root DOM element of the Livewire component...
    $el,

    // Access the ID of the current Livewire component...
    $id,

    // Get the value of a property by name...
    // Usage: $wire.$get('count')
    $get(name) { ... },

    // Set a property on the component by name...
    // Usage: $wire.$set('count', 5)
    $set(name, value, live = true) { ... },

    // Toggle the value of a boolean property...
    $toggle(name, live = true) { ... },

    // Call the method...
    // Usage: $wire.$call('increment')
    $call(method, ...params) { ... },

    // Define a JavaScript action...
    // Usage: $wire.$js('increment', () => { ... })
    // Usage: $wire.$js.increment = () => { ... }
    $js(name, callback) { ... },

    // [DEPRECATED] Entangle - You probably don't need this.
    // Use $wire directly to access properties instead.
    // Usage: <div x-data="{ count: $wire.$entangle('count') }">
    $entangle(name, live = false) { ... },

    // Watch the value of a property for changes...
    // Usage: Alpine.$watch('count', (value, old) => { ... })
    $watch(name, callback) { ... },

    // Scope the next action to a named island...
    // Returns $wire so you can chain any method call.
    // The chained action will only re-render the named island.
    // Usage: $wire.$island('revenue').$refresh()
    // Usage: $wire.$island('feed', { mode: 'append' }).loadMore()
    $island(name, options = {}) { ... },

    // Refresh a component by sending a message to the server
    // to re-render the HTML and swap it into the page...
    $refresh() { ... },

    // Identical to the above `$refresh`. Just a more technical name...
    $commit() { ... }, // Alias for $refresh()

    // Listen for a an event dispatched from this component or its children...
    // Usage: $wire.$on('post-created', () => { ... })
    $on(event, callback) { ... },

    // Listen for a lifecycle hook triggered from this component or the request...
    // Usage: $wire.$hook('message.sent', () => { ... })
    $hook(name, callback) { ... },

    // Dispatch an event from this component...
    // Usage: $wire.$dispatch('post-created', { postId: 2 })
    $dispatch(event, params = {}) { ... },

    // Dispatch an event onto another component...
    // Usage: $wire.$dispatchTo('dashboard', 'post-created', { postId: 2 })
    $dispatchTo(otherComponentName, event, params = {}) { ... },

    // Dispatch an event onto this component and no others...
    $dispatchSelf(event, params = {}) { ... },

    // A JS API to upload a file directly to component
    // rather than through `wire:model`...
    $upload(
        name, // The property name
        file, // The File JavaScript object
        finish = () => { ... }, // Runs when the upload is finished...
        error = () => { ... }, // Runs if an error is triggered mid-upload...
        progress = (event) => { // Runs as the upload progresses...
            event.detail.progress // An integer from 1-100...
        },
    ) { ... },

    // API to upload multiple files at the same time...
    $uploadMultiple(name, files, finish, error, progress) { },

    // Remove an upload after it's been temporarily uploaded but not saved...
    $removeUpload(name, tmpFilename, finish, error) { ... },

    // Register an action interceptor for this component instance
    // Usage: $wire.intercept(({ action, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => { ... })
    // Or scope to specific action: $wire.intercept('save', ({ action, onSuccess }) => { ... })
    intercept(actionOrCallback, callback) { ... },

    // Alias for intercept
    interceptAction(actionOrCallback, callback) { ... },

    // Register a message interceptor for this component instance
    // Usage: $wire.interceptMessage(({ message, cancel, onSend, onCancel, onSuccess, onSkipped, onError, onFailure, onFinish }) => { ... })
    // Or scope to specific action: $wire.interceptMessage('save', callback)
    interceptMessage(actionOrCallback, callback) { ... },

    // Register a request interceptor for this component instance
    // Usage: $wire.interceptRequest(({ request, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => { ... })
    // Or scope to specific action: $wire.interceptRequest('save', callback)
    interceptRequest(actionOrCallback, callback) { ... },

    // Retrieve the underlying "component" object...
    __instance() { ... },
}

你可以在 Livewire 关于从 JavaScript 访问属性的文档中了解更多关于 $wire 的内容。

snapshot 对象

在每次网络请求之间,Livewire 会将 PHP 组件序列化为可在 JavaScript 中消费的对象。该快照用于将组件反序列化回 PHP 对象,因此内置了防篡改机制:

js
let snapshot = {
    // The serialized state of the component (public properties)...
    data: { count: 0 },

    // Long-standing information about the component...
    memo: {
        // The component's unique ID...
        id: '0qCY3ri9pzSSMIXPGg8F',

        // The component's name. Ex. <livewire:[name] />
        name: 'counter',

        // The URI, method, and locale of the web page that the
        // component was originally loaded on. This is used
        // to re-apply any middleware from the original request
        // to subsequent component update requests (messages)...
        path: '/',
        method: 'GET',
        locale: 'en',

        // A list of any nested "child" components. Keyed by
        // internal template ID with the component ID as the values...
        children: [],

        // Whether or not this component was "lazy loaded"...
        lazyLoaded: false,

        // A list of any validation errors thrown during the
        // last request...
        errors: [],
    },

    // A securely encrypted hash of this snapshot. This way,
    // if a malicious user tampers with the snapshot with
    // the goal of accessing un-owned resources on the server,
    // the checksum validation will fail and an error will
    // be thrown...
    checksum: '1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7',
}

component 对象

页面上的每个组件在幕后都有一个对应的 component 对象,用于跟踪其状态并暴露底层功能。这比 $wire 更深一层,仅面向高级用法。

下面是上述 Counter 组件的实际 component 对象,相关属性的说明写在 JS 注释中:

js
let component = {
    // The root HTML element of the component...
    el: HTMLElement,

    // The unique ID of the component...
    id: '0qCY3ri9pzSSMIXPGg8F',

    // The component's "name" (<livewire:[name] />)...
    name: 'counter',

    // The latest "effects" object. Effects are "side-effects" from server
    // round-trips. These include redirects, file downloads, etc...
    effects: {},

    // The component's last-known server-side state...
    canonical: { count: 0 },

    // The component's mutable data object representing its
    // live client-side state...
    ephemeral: { count: 0 },

    // A reactive version of `this.ephemeral`. Changes to
    // this object will be picked up by AlpineJS expressions...
    reactive: Proxy,

    // A Proxy object that is typically used inside Alpine
    // expressions as `$wire`. This is meant to provide a
    // friendly JS object interface for Livewire components...
    $wire: Proxy,

    // A list of any nested "child" components. Keyed by
    // internal template ID with the component ID as the values...
    children: [],

    // The last-known "snapshot" representation of this component.
    // Snapshots are taken from the server-side component and used
    // to re-create the PHP object on the backend...
    snapshot: {...},

    // The un-parsed version of the above snapshot. This is used to send back to the
    // server on the next roundtrip because JS parsing messes with PHP encoding
    // which often results in checksum mis-matches.
    snapshotEncoded: '{"data":{"count":0},"memo":{"id":"0qCY3ri9pzSSMIXPGg8F","name":"counter","path":"\/","method":"GET","children":[],"lazyLoaded":true,"errors":[],"locale":"en"},"checksum":"1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7"}',
}

message 载荷

在浏览器中对 Livewire 组件执行动作时,会触发一次网络请求。该请求包含一个或多个组件以及对服务器的各种指令。在内部,这些组件网络载荷被称为「messages」。

「message」表示组件需要更新时从前端发往后端的数据。组件在前端渲染与操作,直到执行了需要将状态与更新以 message 形式发往后端的动作。

你会在浏览器开发者工具的网络面板载荷中,或在 Livewire 的 JavaScript 钩子里认出这一结构:

js
let message = {
    // Snapshot object...
    snapshot: { ... },

    // A key-value pair list of properties
    // to update on the server...
    updates: {},

    // An array of methods (with parameters) to call server-side...
    calls: [
        { method: 'increment', params: [] },
    ],
}