变形
当 Livewire 组件更新浏览器的 DOM 时,它会以一种我们称之为“变形”的智能方式进行。术语变形与替换等词形成对比。
Livewire 不会在每次更新组件时用新渲染的 HTML替换组件的 HTML,而是动态比较当前 HTML 和新 HTML,识别差异,并仅在需要更改的地方对 HTML 进行手术更改。
这样做的好处是保留组件上现有的未更改元素。例如,事件侦听器、焦点状态和表单输入值在 Livewire 更新之间都会保留。当然,与每次更新时擦除并重新渲染新 DOM 相比,变形还提供了更高的性能。
变形的工作原理
要了解 Livewire 如何确定在 Livewire 请求之间更新哪些元素,请考虑这个简单的 Todos
组件
class Todos extends Component{ public $todo = ''; public $todos = [ 'first', 'second', ]; public function add() { $this->todos[] = $this->todo; }}
<form wire:submit="add"> <ul> @foreach ($todos as $item) <li>{{ $item }}</li> @endforeach </ul> <input wire:model="todo"></form>
此组件的初始渲染将输出以下 HTML
<form wire:submit="add"> <ul> <li>first</li> <li>second</li> </ul> <input wire:model="todo"></form>
现在,想象你在输入字段中输入“third”并按下 [Enter]
键。新渲染的 HTML 将是
<form wire:submit="add"> <ul> <li>first</li> <li>second</li> + <li>third</li> </ul> <input wire:model="todo"> </form>
当 Livewire 处理组件更新时,它会将原始 DOM变形为新渲染的 HTML。以下可视化应该可以直观地让你了解它是如何工作的
正如你所看到的,Livewire 同时遍历两个 HTML 树。当它在两个树中遇到每个元素时,它会比较它们是否有更改、添加和删除。如果检测到一个,它会通过手术进行适当的更改。
变形的缺点
以下是在 HTML 树中无法正确识别更改的变形算法场景,因此会导致应用程序出现问题。
插入中间元素
考虑以下用于虚构的 CreatePost
组件的 Livewire Blade 模板
<form wire:submit="save"> <div> <input wire:model="title"> </div> @if ($errors->has('title')) <div>{{ $errors->first('title') }}</div> @endif <div> <button>Save</button> </div></form>
如果用户尝试提交表单,但遇到验证错误,则会出现以下问题
如您所见,当 Livewire 遇到用于错误消息的新 <div>
时,它不知道是否要就地更改现有的 <div>
,还是在中间插入新的 <div>
。
更明确地重申一下正在发生的事情
- Livewire 在两棵树中都遇到第一个
<div>
。它们是相同的,所以它继续。 - Livewire 在两棵树中都遇到第二个
<div>
,并认为它们是相同的<div>
,只是其中一个更改了内容。因此,它没有将错误消息插入为新元素,而是将<button>
更改为错误消息。 - 然后,Livewire 在错误地修改了上一个元素后,注意到在比较的末尾有一个额外的元素。然后,它创建并追加元素在前面一个元素之后。
- 因此,销毁,然后重新创建本应简单移动的元素。
此场景几乎是所有与变形相关的错误的根源。
以下是这些错误的一些具体问题影响
- 事件侦听器和元素状态在更新之间丢失
- 事件侦听器和状态错误地放置在错误的元素上
- 整个 Livewire 组件可以重置或复制,因为 Livewire 组件在 DOM 树中也是简单的元素
- Alpine 组件和状态可以丢失或错位
幸运的是,Livewire 已经努力使用以下方法来减轻这些问题
内部前瞻
Livewire 在其变形算法中有一个额外的步骤,在更改元素之前检查后续元素及其内容。
这在很多情况下可以防止上述情况发生。
以下是“预测”算法实际操作的可视化效果
注入变形标记
在后端,Livewire 会自动检测 Blade 模板中的条件语句,并将它们包装在 HTML 注释标记中,Livewire 的 JavaScript 可以在变形时使用这些标记作为指南。
以下是上一个 Blade 模板的示例,但带有 Livewire 注入的标记
<form wire:submit="save"> <div> <input wire:model="title"> </div> <!--[if BLOCK]><![endif]--> @if ($errors->has('title')) <div>Error: {{ $errors->first('title') }}</div> @endif <!--[if ENDBLOCK]><![endif]--> <div> <button>Save</button> </div></form>
通过将这些标记注入到模板中,Livewire 现在可以更容易地检测到更改和添加之间的差异。
此功能对 Livewire 应用程序非常有益,但由于它需要通过正则表达式解析模板,因此有时可能无法正确检测条件语句。如果此功能对您的应用程序来说弊大于利,则可以使用应用程序的 config/livewire.php
文件中的以下配置将其禁用
'inject_morph_markers' => false,
包装条件语句
如果上述两个解决方案不适用于您的情况,避免变形问题最可靠的方法是将条件语句和循环包装在始终存在的自己的元素中。
例如,以下是使用包装 <div>
元素重写的上述 Blade 模板
<form wire:submit="save"> <div> <input wire:model="title"> </div> <div> @if ($errors->has('title')) <div>{{ $errors->first('title') }}</div> @endif </div> <div> <button>Save</button> </div></form>
现在条件语句已包装在持久元素中,Livewire 将正确变形两个不同的 HTML 树。