Skip to content
全部文档

水合(Hydration)

使用 Livewire 的感觉就像把服务端 PHP 类直接挂到浏览器上。诸如通过按钮点击直接调用服务端函数,都强化了这种错觉。但实际上,它只是一种错觉。

在幕后,Livewire 的行为更接近标准 Web 应用:向浏览器渲染静态 HTML,监听浏览器事件,再通过 AJAX 请求调用服务端代码。

由于 Livewire 发往服务器的每次 AJAX 请求都是「无状态」的(没有长期运行的后端进程来维持组件状态),Livewire 必须在做任何更新之前,根据上次已知状态重建组件。

做法是:在每次服务端更新后对 PHP 组件拍「快照」,以便在下次请求时重建或_恢复_组件。

本文档中,我们将拍快照的过程称为「脱水(dehydration)」,将从快照重建组件的过程称为「水合(hydration)」。

脱水

当 Livewire 脱水 服务端组件时,会做两件事:

  • 将组件模板渲染为 HTML
  • 创建组件的 JSON 快照

渲染 HTML

组件挂载或完成更新后,Livewire 会调用组件的 render() 方法,把 Blade 模板转为原始 HTML。

以如下 counter 组件为例:

php
<?php

use Livewire\Component;

new class extends Component {
    public $count = 1;

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

    public function render()
    {
        return <<<'HTML'
        <div>
            Count: {{ $count }}

            <button wire:click="increment">+</button>
        </div>
        HTML;
    }
};

每次挂载或更新后,Livewire 会把上述 counter 组件渲染为如下 HTML:

html
<div>
    Count: 1

    <button wire:click="increment">+</button>
</div>

快照

为了在下次请求时于服务端重建 counter 组件,会创建一个 JSON 快照,尽可能多地捕获组件状态:

js
{
    state: {
        count: 1,
    },

    memo: {
        name: 'counter',

        id: '1526456',
    },
}

注意快照有两个不同部分:memostate

memo 用于存放识别并重建组件所需的信息,state 则存放组件所有公共属性的值。

INFO

上面的快照是 Livewire 实际快照的精简版。在真实应用中,快照会包含更多信息,例如验证错误、子组件列表、语言区域等。若要更详细了解快照对象,可参阅快照结构文档

将快照嵌入 HTML

组件首次渲染时,Livewire 会把快照以 JSON 形式存在名为 wire:snapshot 的 HTML 属性中。这样 Livewire 的 JavaScript 核心就能提取 JSON 并转为运行时对象:

html
<div wire:id="..." wire:snapshot="{ state: {...}, memo: {...} }">
    Count: 1

    <button wire:click="increment">+</button>
</div>

水合

触发组件更新时(例如在 counter 组件中按下「+」按钮),会向服务器发送类似如下的载荷:

js
{
    calls: [
        { method: 'increment', params: [] },
    ],

    snapshot: {
        state: {
            count: 1,
        },

        memo: {
            name: 'counter',

            id: '1526456',
        },
    }
}

在 Livewire 能调用 increment 方法之前,必须先创建新的 counter 实例,并用快照中的状态填充它。

下面是实现该结果的 PHP 伪代码:

php
$state = request('snapshot.state');
$memo = request('snapshot.memo');

$instance = Livewire::new($memo['name'], $memo['id']);

foreach ($state as $property => $value) {
    $instance[$property] = $value;
}

顺着上面的脚本可以看到:创建 counter 对象后,会根据快照提供的状态设置其公共属性。

进阶水合

上面的 counter 示例很适合说明水合概念;但它只演示了 Livewire 如何水合整数(1)这类简单值。

如你所知,Livewire 还支持远比整数更复杂的属性类型。

再看一个稍复杂的例子——todos 组件:

php
<?php

use Livewire\Component;

new class extends Component {
    public $todos;

    public function mount() {
        $this->todos = collect([
            'first',
            'second',
            'third',
        ]);
    }
};

可以看到,我们把 $todos 属性设为一个包含三个字符串的 Laravel 集合

单靠 JSON 无法表示 Laravel 集合,因此 Livewire 设计了自己的模式:在快照中把元数据与纯数据关联起来。

todos 组件快照的 state 对象如下:

js
state: {
    todos: [
        [ 'first', 'second', 'third' ],
        { s: 'clctn', class: 'Illuminate\\Support\\Collection' },
    ],
},

如果你预期的是更直白的形式,这可能会让人困惑:

js
state: {
    todos: [ 'first', 'second', 'third' ],
},

然而,若 Livewire 基于这种数据水合组件,就无法知道它是集合而不是普通数组。

因此,Livewire 支持一种备选状态语法——元组(含两项的数组):

js
todos: [
    [ 'first', 'second', 'third' ],
    { s: 'clctn', class: 'Illuminate\\Support\\Collection' },
],

水合组件状态时若遇到元组,Livewire 会用元组第二项中的信息,更智能地水合第一项中存放的状态。

为更清楚演示,下面是简化代码,展示 Livewire 如何根据上述快照重建集合属性:

php
[ $state, $metadata ] = request('snapshot.state.todos');

$collection = new $metadata['class']($state);

可以看到,Livewire 使用与状态关联的元数据来得到完整的集合类。

深层嵌套元组

这种方法的一个显著优势是能够脱水与水合深层嵌套的属性。

例如,仍用上面的 todos 示例,但集合第三项改为 Laravel Stringable,而不是普通字符串:

php
<?php

use Livewire\Component;

new class extends Component {
    public $todos;

    public function mount() {
        $this->todos = collect([
            'first',
            'second',
            str('third'),
        ]);
    }
};

该组件状态脱水后的快照会变成这样:

js
todos: [
    [
        'first',
        'second',
        [ 'third', { s: 'str' } ],
    ],
    { s: 'clctn', class: 'Illuminate\\Support\\Collection' },
],

可以看到,集合第三项已脱水为元数据元组:第一项是普通字符串值,第二项是告知 Livewire 该字符串是 stringable 的标记。

支持自定义属性类型

内部上,Livewire 已为最常见的 PHP 与 Laravel 类型提供水合支持。若要支持尚未支持的类型,可使用 Synthesizers——Livewire 用于水合/脱水非原始属性类型的内部机制。

另见