JavaScript

未匹配的标注
本文档最新版为 2.x,旧版本可能放弃维护,推荐阅读最新版!

Livewire provides plenty of JavaScript extension points for advanced users who want to use Livewire in deeper ways or extend Livewire’s features with custom APIs.

Global Livewire events

Livewire dispatches two helpful browser events for you to register any custom extension points:

<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]
It is often beneficial to register any custom directives or lifecycle hooks inside of livewire:init so that they are available before Livewire begins initializing on the page.

The Livewire global object

Livewire’s global object is the best starting point for interacting with Livewire in JavaScript.

You can access the global Livewire JavaScript object on window from anywhere inside your client-side code.

It is often helpful to use window.Livewire inside a livewire:init event listener

Accessing components

You can use the following methods to access specific Livewire components loaded on the current page:

// 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]
Each of these methods returns a $wire object representing the component’s state in Livewire.



You can learn more about these objects in the $wire documentation.

Interacting with events

In addition to dispatching and listening for events from individual components in PHP, the global Livewire object allows you interact with Livewire’s event system from anywhere in your application:

// 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 }) => {
    // ...
})

Accessing hooks

Livewire allows you to hook into various parts of its internal lifecycle using Livewire.hook():

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

More information about Livewire’s JavaScript hooks can be found below.

Registering custom directives

Livewire allows you to register custom directives using Livewire.directive().

Below is an example of a custom wire:confirm directive that uses JavaScript’s confirm() dialog to confirm or cancel an action before it is sent to the server:

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

Here is the implementation of wire:confirm using Livewire.directive():

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)
    })
})

Controlling Livewire’s initialization

In general, you shouldn’t need to manually start or stop Livewire, however, if you find yourself needing this behavior, Livewire makes it available to you via the following methods:

// Start Livewire on a page that doesn't have Livewire running...
Livewire.start()

// Stop Livewire and teardown its JavaScript runtime
// (remove event listeners and such)...
Livewire.stop()

// Force Livewire to scan the DOM for any components it may have missed...
Livewire.rescan()

Object schemas

When extending Livewire’s JavaScript system, it’s important to understand the different objects you might encounter.

Here is an exhaustive reference of each of Livewire’s relevant internal properties.

As a reminder, the average Livewire user may never interact with these. Most of these objects are available for Livewire’s internal system or advanced users.

The $wire object

Given the following generic Counter component:

<?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 exposes a JavaScript representation of the server-side component in the form of an object that is commonly referred to as $wire:

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,

    // 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) { ... },

    // Entangle the value of a Livewire property with a different,
    // arbitrary, Alpine property...
    // 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) { ... },

    // Refresh a component by sending a commit 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() { ... },

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

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

You can learn more about $wire in Livewire’s documentation on accessing properties in JavaScript.

The snapshot object

Between each network request, Livewire serializes the PHP component into an object that can be consumed in JavaScript. This snapshot is used to unserialize the component back into a PHP object and therefore has mechanisms built in to prevent tampering:

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 (commits)...
        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: [],

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

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

    // A securely encryped hash of this snapshot. This way,
    // if a malicous 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',
}

The component object

Every component on a page has a corresponding component object behind the scenes keeping track of its state and exposing its underlying functionality. This is one layer deeper than $wire. It is only meant for advanced usage.

Here’s an actual component object for the above Counter component with descriptions of relevant properties in JS comments:

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"}',
}

The commit payload

When an action is performed on a Livewire component in the browser, a network request is triggered. That network request contains one or many components and various instructions for the server. Internally, these component network payloads are called “commits”.

The term “commit” was chosen as a helpful way to think about Livewire’s relationship between frontend and backend. A component is rendered and manipulated on the frontend until an action is performed that requires it to “commit” its state and updates to the backend.

You will recognize this schema from the payload in the network tab of your browser’s DevTools, or Livewire’s JavaScript hooks:

let commit = {
    // 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: [] },
    ],
}

JavaScript hooks

For advanced users, Livewire exposes its internal client-side “hook” system. You can use the following hooks to extend Livewire’s functionality or gain more information about your Livewire application.

Component initialization

Every time a new component is discovered by Livewire — whether on the initial page load or later on — the component.init event is triggered. You can hook into component.init to intercept or initialize anything related to the new component:

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

For more information, please consult the documentation on the component object.

DOM element initialization

In addition to triggering an event when new components are initialized, Livewire triggers an event for each DOM element within a given Livewire component.

This can be used to provide custom Livewire HTML attributes within your application:

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

DOM Morph hooks

During the DOM morphing phase—which occurs after Livewire completes a network roundtrip—Livewire triggers a series of events for every element that is mutated.

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 }) => {
    //
})

Commit hooks

Because Livewire requests contain multiple components, request is too broad of a term to refer to an individual component’s request and response payload. Instead, internally, Livewire refers to component updates as commits — in reference to committing component state to the server.

These hooks expose commit objects. You can learn more about their schema by reading the commit object documentation.

Preparing commits

The commit.prepare hook will be triggered immediately before a request is sent to the server. This gives you a chance to add any last minute updates or actions to the outgoing request:

Livewire.hook('commit.prepare', ({ component, commit }) => {
    // Runs before commit payloads are collected and sent to the server...
})

Intercepting commits

Every time a Livewire component is sent to the server, a commit is made. To hook into the lifecycle and contents of an individual commit, Livewire exposes a commit hook.

This hook is extremely powerful as it provides methods for hooking into both the request and response of a Livewire commit:

Livewire.hook('commit', ({ component, commit, respond, succeed, fail }) => {
    // Runs immediately before a commit's payload is sent to the server...

    respond(() => {
        // Runs after a response is received but before it's processed...
    })

    succeed(({ snapshot, effect }) => {
        // Runs after a successful response is received and processed
        // with a new snapshot and list of effects...
    })

    fail(() => {
        // Runs if some part of the request failed...
    })
})

Request hooks

If you would like to instead hook into the entire HTTP request going and returning from the server, you can do so using the request hook:

Livewire.hook('request', ({ uri, options, payload, respond, succeed, fail }) => {
    // Runs after commit payloads are compiled, but before a network request is sent...

    respond(({ status, response }) => {
        // Runs when the response is received...
        // "response" is the raw HTTP response object
        // before await response.text() is run...
    })

    succeed(({ status, json }) => {
        // Runs when the response is received...
        // "json" is the JSON response object...
    })

    fail(({ status, content, preventDefault }) => {
        // Runs when the response has an error status code...
        // "preventDefault" allows you to disable Livewire's
        // default error handling...
        // "content" is the raw response content...
    })
})

Customizing page expiration behavior

If the default page expired dialog isn’t suitable for your application, you can implement a custom solution using the request hook:

<script>
    document.addEventListener('livewire:init', () => {
        Livewire.hook('request', ({ fail }) => {
            fail(({ status, preventDefault }) => {
                if (status === 419) {
                    confirm('Your custom page expiration behavior...')

                    preventDefault()
                }
            })
        })
    })
</script>

With the above code in your application, users will receive a custom dialog when their session has expired.

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~