主题
Hydration
使用 Livewire 感觉就像将服务端 PHP 类直接附加到 Web 浏览器上。像直接从按钮点击调用服务端函数这样的功能支持了这种错觉。但实际上,这只是一种错觉。
在后台,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',
},
}注意快照的两个不同部分:memo 和 state.
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 组件的快照状态对象:
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 用于水合/脱水非原始属性类型的内部机制.
另请参阅
- 生命周期钩子 — 使用 hydrate() 和 dehydrate() 钩子
- 属性 — 属性如何在请求之间保留
- Morphing — Livewire 如何更新 DOM
- Synthesizers — 自定义属性序列化