从 V2 升级
以下是 Alpine V3 中破坏性变更的详尽指南,如果你想要更生动的方式,可以通过观看 Alpine Day 2021 的"Alpine 的未来"主题演讲来了解 V3 中的所有变更和新功能:
从 Alpine V2 升级到 V3 应该相当容易。在很多情况下,你的代码库无需做任何修改就能使用 V3。以下是对破坏性变更和弃用内容的详尽列表,按用户可能受影响的程度从高到低排列:
注意:如果你同时使用 Laravel Livewire 和 Alpine,要在 V3 中使用 Alpine,你需要将 Livewire 升级到 v2.5.1 或更高版本。
破坏性变更
$el现在始终指向当前元素- 自动评估数据对象上定义的
init()函数 - 导入后需要调用
Alpine.start() x-show.transition现在是x-transitionx-if不再支持x-transitionx-data层叠作用域x-init不再接受回调返回值- 从事件处理器返回
false不再隐式调用 "preventDefault" x-spread现在是x-bindx-ref不再支持动态绑定- 使用全局生命周期事件替代
Alpine.deferLoadingAlpine() - 不再支持 IE11
$el 现在始终指向当前元素
$el 现在始终代表执行表达式的元素,而不是组件的根元素。这将取代大多数 x-ref 的使用场景。如果你仍然需要访问组件的根元素,可以使用 $root。例如:
<!-- 🚫 之前 -->
<div x-data>
<button @click="console.log($el)"></button>
<!-- 在 V2 中,$el 指向 <div>,现在指向 <button> -->
</div>
<!-- ✅ 之后 -->
<div x-data>
<button @click="console.log($root)"></button>
</div>
为了更平滑的升级体验,你可以将所有 $el 替换为一个名为 $root 的自定义魔术属性。
→ 了解更多关于 V3 中的 $el
→ 了解更多关于 V3 中的 $root
自动评估数据对象上定义的 init() 函数
在 V2 中,常见的模式是在 x-data 对象上手动调用 init()(或类似命名的方法)。
在 V3 中,Alpine 会自动调用数据对象上的 init() 方法。
<!-- 🚫 之前 -->
<div x-data="foo()" x-init="init()"></div>
<!-- ✅ 之后 -->
<div x-data="foo()"></div>
<script>
function foo() {
return {
init() {
//
}
}
}
</script>
导入后需要调用 Alpine.start()
如果你从 NPM 导入 Alpine V2,现在需要手动调用 Alpine.start() 才能使用 V3。如果你使用 Alpine 的构建文件或通过 <template> 标签使用 CDN,则不受影响。
// 🚫 之前
import 'alpinejs'
// ✅ 之后
import Alpine from 'alpinejs'
window.Alpine = Alpine
Alpine.start()
x-show.transition 现在是 x-transition
x-show.transition... 辅助方法提供的所有便利功能仍然可用,但现在通过更统一的 API:x-transition 来实现:
<!-- 🚫 之前 -->
<div x-show.transition="open"></div>
<!-- ✅ 之后 -->
<div x-show="open" x-transition></div>
<!-- 🚫 之前 -->
<div x-show.transition.duration.500ms="open"></div>
<!-- ✅ 之后 -->
<div x-show="open" x-transition.duration.500ms></div>
<!-- 🚫 之前 -->
<div x-show.transition.in.duration.500ms.out.duration.750ms="open"></div>
<!-- ✅ 之后 -->
<div
x-show="open"
x-transition:enter.duration.500ms
x-transition:leave.duration.750ms
></div>
x-if 不再支持 x-transition
在 Alpine 中,不再支持在元素从 DOM 中添加/移除之前或之后为其添加过渡效果。
这个功能很少有人知道,更不用说使用了。
由于过渡系统很复杂,从维护角度来看,只支持使用 x-show 的元素进行过渡更为合理。
<!-- 🚫 之前 -->
<template x-if.transition="open">
<div>...</div>
</template>
<!-- ✅ 之后 -->
<div x-show="open" x-transition>...</div>
x-data 层叠作用域
x-data 中定义的作用域现在对其所有子元素可用,除非被嵌套的 x-data 表达式覆盖。
<!-- 🚫 之前 -->
<div x-data="{ foo: 'bar' }">
<div x-data="{}">
<!-- foo 未定义 -->
</div>
</div>
<!-- ✅ 之后 -->
<div x-data="{ foo: 'bar' }">
<div x-data="{}">
<!-- foo 的值为 'bar' -->
</div>
</div>
x-init 不再接受回调返回值
在 V3 之前,如果 x-init 接收到的返回值 typeof 为 "function",Alpine 会在完成初始化树中所有其他指令后执行该回调。现在,你需要手动调用 $nextTick() 来实现相同的行为。x-init 不再"感知返回值"。
<!-- 🚫 之前 -->
<div x-data x-init="() => { ... }">...</div>
<!-- ✅ 之后 -->
<div x-data x-init="$nextTick(() => { ... })">...</div>
从事件处理器返回 false 不再隐式调用 "preventDefault"
Alpine V2 将返回值为 false 视为希望对该事件执行 preventDefault。这符合原生内联监听器的标准行为:<... oninput="someFunctionThatReturnsFalse()">。Alpine V3 不再支持这种 API。大多数人不知道它的存在,因此这种行为会令人困惑。
<!-- 🚫 之前 -->
<div x-data="{ blockInput() { return false } }">
<input type="text" @input="blockInput()">
</div>
<!-- ✅ 之后 -->
<div x-data="{ blockInput(e) { e.preventDefault() }">
<input type="text" @input="blockInput($event)">
</div>
x-spread 现在是 x-bind
Alpine 复用功能的方式之一是将 Alpine 指令抽象为对象,然后通过 x-spread 应用于元素。这种行为保持不变,只是现在 x-bind(不指定属性)替代了 x-spread 成为新的 API。
<!-- 🚫 之前 -->
<div x-data="dropdown()">
<button x-spread="trigger">切换</button>
<div x-spread="dialogue">...</div>
</div>
<!-- ✅ 之后 -->
<div x-data="dropdown()">
<button x-bind="trigger">切换</button>
<div x-bind="dialogue">...</div>
</div>
<script>
function dropdown() {
return {
open: false,
trigger: {
'x-on:click'() { this.open = ! this.open },
},
dialogue: {
'x-show'() { return this.open },
'x-bind:class'() { return 'foo bar' },
},
}
}
</script>
使用全局生命周期事件替代 Alpine.deferLoadingAlpine()
<!-- 🚫 之前 -->
<script>
window.deferLoadingAlpine = startAlpine => {
// 将在初始化 Alpine 之前执行。
startAlpine()
// 将在初始化 Alpine 之后执行。
}
</script>
<!-- ✅ 之后 -->
<script>
document.addEventListener('alpine:init', () => {
// 将在初始化 Alpine 之前执行。
})
document.addEventListener('alpine:initialized', () => {
// 将在初始化 Alpine 之后执行。
})
</script>
x-ref 不再支持动态绑定
在 Alpine V2 中,对于以下代码:
<div x-data="{options: [{value: 1}, {value: 2}, {value: 3}] }">
<div x-ref="0">0</div>
<template x-for="option in options">
<div :x-ref="option.value" x-text="option.value"></div>
</template>
<button @click="console.log($refs[0], $refs[1], $refs[2], $refs[3]);">显示 $refs</button>
</div>
点击按钮后,所有 $refs 都会显示。但在 Alpine V3 中,只能访问静态创建的元素的 $refs,因此只有第一个 ref 会按预期返回。
不再支持 IE11
Alpine 将不再正式支持 Internet Explorer 11。如果你需要支持 IE11,建议仍使用 Alpine V2。
弃用的 API
以下 2 个 API 在 V3 中仍然可用,但已被标记为弃用,并可能在将来某个时候被移除。
事件监听器修饰符 .away 应替换为 .outside
<!-- 🚫 之前 -->
<div x-show="open" @click.away="open = false">
...
</div>
<!-- ✅ 之后 -->
<div x-show="open" @click.outside="open = false">
...
</div>
优先使用 Alpine.data() 而非全局 Alpine 函数数据提供者
<!-- 🚫 之前 -->
<div x-data="dropdown()">
...
</div>
<script>
function dropdown() {
return {
...
}
}
</script>
<!-- ✅ 之后 -->
<div x-data="dropdown">
...
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
...
}))
})
</script>
注意,你需要在调用
Alpine.start()之前定义Alpine.data()扩展。更多信息请参考生命周期问题和作为模块安装文档页面。