水化

您是视觉学习者吗?
通过我们深入的屏幕录制掌握 Livewire
立即观看

使用 Livewire 感觉就像将服务器端 PHP 类直接附加到 Web 浏览器上。诸如直接从按钮按下调用服务器端函数之类的功能支持了这种错觉。但实际上,它只是:一种错觉。

在后台,Livewire 实际上更像一个标准 Web 应用程序。它向浏览器呈现静态 HTML,侦听浏览器事件,然后发出 AJAX 请求来调用服务器端代码。

由于 Livewire 向服务器发出的每个 AJAX 请求都是“无状态”(这意味着没有长期运行的后端进程来保持组件的状态),因此 Livewire 必须在进行任何更新之前重新创建组件的最后已知状态。

它通过在每次服务器端更新后对 PHP 组件进行“快照”来实现此目的,以便可以在下一次请求中重新创建或恢复组件。

在整个文档中,我们将把获取快照的过程称为“脱水”,并将从快照重新创建组件的过程称为“水化”。

脱水

当 Livewire 对服务器端组件进行脱水时,它会做两件事

  • 将组件的模板呈现为 HTML
  • 创建组件的 JSON 快照

呈现 HTML

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

以以下 Counter 组件为例

class Counter 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

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

快照

为了在下次请求期间在服务器上重新创建 Counter 组件,将创建一个 JSON 快照,尝试尽可能多地捕获组件的状态

{
state: {
count: 1,
},
 
memo: {
name: 'counter',
 
id: '1526456',
},
}

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

memo 部分用于存储识别和重新创建组件所需的信息,而 state 部分存储组件所有公共属性的值。

上述快照是 Livewire 中实际快照的精简版本。在实时应用程序中,快照包含更多信息,例如验证错误、子组件列表、语言环境等。有关快照对象的更详细了解,可以参考 快照架构文档

将快照嵌入 HTML

当组件首次渲染时,Livewire 将快照作为 JSON 存储在名为 wire:snapshot 的 HTML 属性中。这样,Livewire 的 JavaScript 核心就可以提取 JSON 并将其转换为运行时对象

<div wire:id="..." wire:snapshot="{ state: {...}, memo: {...} }">
Count: 1
 
<button wire:click="increment">+</button>
</div>

水化

当触发组件更新时,例如,在 Counter 组件中按下“+”按钮,会向服务器发送以下负载

{
calls: [
{ method: 'increment', params: [] },
],
 
snapshot: {
state: {
count: 1,
},
 
memo: {
name: 'counter',
 
id: '1526456',
},
}
}

在 Livewire 调用 increment 方法之前,它必须先创建一个新的 Counter 实例,并使用快照的状态对其进行填充。

以下是一些实现此结果的 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组件

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

如你所见,我们将$todos属性设置为Laravel 集合,其中包含三个字符串作为其内容。

JSON 本身无法表示 Laravel 集合,因此,Livewire 创建了自己的模式,在快照中将元数据与纯数据关联起来。

以下是此Todos组件的快照状态对象

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

如果你期望更直接的内容,例如

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

这可能会让你感到困惑。

但是,如果 Livewire 根据此数据对组件进行水合,它将无法知道这是一个集合,而不是一个普通数组。

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

因此,Livewire 支持以元组(一个包含两项的数组)的形式使用备用状态语法

当 Livewire 在对组件状态进行水合时遇到元组时,它会使用存储在元组第二个元素中的信息,以更智能的方式对存储在第一个元素中的状态进行水合。

[ $state, $metadata ] = request('snapshot.state.todos');
 
$collection = new $metadata['class']($state);

为了更清楚地演示,这里有简化代码,展示 Livewire 如何根据上述快照重新创建集合属性

如你所见,Livewire 使用与状态关联的元数据来派生完整的集合类。

深度嵌套元组

此方法的一个明显优势是能够对深度嵌套属性进行脱水和水合。

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

例如,考虑上述Todos示例,但现在在集合中将第三个项目替换为Laravel Stringable,而不是普通字符串

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

此组件状态的脱水快照现在将如下所示

支持自定义属性类型

在内部,Livewire 对最常见的 PHP 和 Laravel 类型提供了水化支持。但是,如果你希望支持不受支持的类型,你可以使用 合成器 来实现 — Livewire 的内部机制,用于对非原始属性类型进行水化/脱水。