跳转至

Morph 插件

Alpine 的 Morph 插件允许你将页面上的一个元素"变形"为提供的 HTML 模板,同时保留"变形"元素中的任何浏览器或 Alpine 状态。

这对于更新来自服务器请求的 HTML 而不会丢失 Alpine 的页面状态非常有用。这样的工具是全栈框架(如 Laravel LivewirePhoenix LiveView)的核心。

理解其用途的最佳方式是通过以下交互式可视化演示。试试吧!

安装

你可以通过 <script> 标签引入或通过 NPM 安装来使用此插件:

通过 CDN

你可以将此插件的 CDN 构建版本作为 <script> 标签引入,只需确保在 Alpine 核心 JS 文件之前引入。

<!-- Alpine 插件 -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"></script>

<!-- Alpine 核心 -->
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>

通过 NPM

你可以从 NPM 安装 Morph 以在打包中使用:

npm install @alpinejs/morph

然后从你的打包工具中初始化它:

import Alpine from 'alpinejs'
import morph from '@alpinejs/morph'

window.Alpine = Alpine
Alpine.plugin(morph)

...

Alpine.morph()

Alpine.morph(el, newHtml) 允许你基于传入的 HTML 强制对 DOM 节点进行变形。它接受以下参数:

参数 描述
el 页面上的一个 DOM 元素。
newHtml 用作变形目标模板的 HTML 字符串。
options(可选) 主要用于注入生命周期钩子的选项对象。

以下是一个使用 Alpine.morph() 用新 HTML 更新 Alpine 组件的示例:(在实际应用中,这个新 HTML 很可能来自服务器)

<div x-data="{ message: 'Change me, then press the button!' }">
    <input type="text" x-model="message">
    <span x-text="message"></span>
</div>

<button>Run Morph</button>

<script>
    document.querySelector('button').addEventListener('click', () => {
        let el = document.querySelector('div')

        Alpine.morph(el, `
            <div x-data="{ message: 'Change me, then press the button!' }">
                <h2>See how new elements have been added</h2>

                <input type="text" x-model="message">
                <span x-text="message"></span>

                <h2>but the state of this component hasn't changed? Magical.</h2>
            </div>
        `)
    })
</script>

生命周期钩子

"Morph" 插件的工作原理是比较两个 DOM 树:实时元素和传入的 HTML。

Morph 同时遍历两棵树,并比较每个节点及其子节点。如果发现差异,它会对当前 DOM 树进行"修补"(更改),以匹配传入的 HTML 树。

虽然默认算法非常强大,但有些情况下你可能希望挂钩到它的生命周期,在变形发生时观察或改变其行为。

在我们深入了解可用的生命周期钩子之前,先列出它们可能接收的所有参数并解释每个参数的作用:

参数 描述
el 这始终是页面上实际、当前的、将被"修补"(由 Morph 更改)的 DOM 元素。
toEl 这是一个"模板元素"。它是一个临时元素,代表实时 el 将被修补成的样子。它永远不会实际存在于页面上,仅应用于参考目的。
childrenOnly() 这是一个可以在钩子内部调用的函数,告诉 Morph 跳过当前元素,只"修补"它的子元素。
skip() 一个函数,在钩子内调用时将"跳过"当前元素及其子元素的比较/修补。

以下是可用的生命周期钩子(作为第三个参数传递给 Alpine.morph(..., options)):

选项 描述
updating(el, toEl, childrenOnly, skip) 在将 el 与比较对象 toEl 进行修补之前调用。
updated(el, toEl) 在 Morph 修补了 el 之后调用。
removing(el, skip) 在 Morph 从实时 DOM 中移除一个元素之前调用。
removed(el) 在 Morph 从实时 DOM 中移除一个元素之后调用。
adding(el, skip) 在添加新元素之前调用。
added(el) 在向实时 DOM 树添加新元素之后调用。
key(el) 一个可重用的函数,用于确定 Morph 如何在比较/修补之前对树中的元素进行"键控"。更多信息请看这里
lookahead 一个布尔值,告诉 Morph 在其算法中启用一项额外的功能,即"向前看",以确保即将被移除的 DOM 元素应该只是"移动"到后面的兄弟位置。

以下代码是所有生命周期钩子的更具体参考:

Alpine.morph(el, newHtml, {
    updating(el, toEl, childrenOnly, skip) {
        //
    },

    updated(el, toEl) {
        //
    },

    removing(el, skip) {
        //
    },

    removed(el) {
        //
    },

    adding(el, skip) {
        //
    },

    added(el) {
        //
    },

    key(el) {
        // 默认情况下 Alpine 使用 `key=""` HTML 属性。
        return el.id
    },

    lookahead: true, // 默认值:false
})

像 Morph 这样的 DOM 差异比较工具会尽力准确地将原始 DOM "变形"为新的 HTML。然而,在某些情况下,无法确定一个元素是应该被修改还是被完全替换。

由于这个限制,Morph 有一个"键"系统,允许开发者"强制"保留某些元素而不是替换它们。

最常见的用例是循环中的同级元素列表。以下是一个有时需要键的原因示例:

<!-- 页面上的"实时" DOM: -->
<ul>
    <li>Mark</li>
    <li>Tom</li>
    <li>Travis</li>
</ul>

<!-- 要"变形为"的新 HTML: -->
<ul>
    <li>Travis</li>
    <li>Mark</li>
    <li>Tom</li>
</ul>

在以上情况下,Morph 无法知道 "Travis" 节点已经在 DOM 树中被移动了。它只会认为 "Mark" 已变为 "Travis",而 "Travis" 已变为 "Tom"。

这不是我们真正想要的,我们希望 Morph 保留原始元素,而不是修改它们,而是在 <ul> 内移动它们。

通过为每个节点添加键,我们可以实现这一点,如下所示:

<!-- 页面上的"实时" DOM: -->
<ul>
    <li key="1">Mark</li>
    <li key="2">Tom</li>
    <li key="3">Travis</li>
</ul>

<!-- 要"变形为"的新 HTML: -->
<ul>
    <li key="3">Travis</li>
    <li key="1">Mark</li>
    <li key="2">Tom</li>
</ul>

现在 <li> 上有了"键",Morph 会在两棵树中匹配它们并相应地移动它们。

你可以使用 key: 配置选项来配置 Morph 将什么视为"键"。更多信息请看这里

Alpine.morphBetween()

Alpine.morphBetween(startMarker, endMarker, newHtml, options) 方法允许你基于传入的 HTML 变形两个标记元素之间的 DOM 节点范围。当你只想更新 DOM 的特定部分而不提供一个单一的根节点时,这很有用。

参数 描述
startMarker 一个 DOM 节点(通常是注释节点),标记要变形范围的开始
endMarker 一个 DOM 节点(通常是注释节点),标记要变形范围的结束
newHtml 一个 HTML 字符串或 DOM 元素,用于替换标记之间的内容
options 选项对象(与 Alpine.morph() 相同)