# Alpine.js 从入门到精通 1 指令

> [Alpine.js 从入门到精通 1 指令](https://kkbt0.github.io/Somnia/docs/alpine.js/alpinejs1/)
> Penned by [恐咖兵糖](https://www.ftls.xyz) on 2025-01-01


Alpine.js 是一个轻量级的 JavaScript 框架，它借鉴了 Vue.js 和 Angular 的思想，但更加精简，可以直接在 HTML 中通过指令来管理状态和行为，非常适合需要少量交互的静态网站或增强渐进式应用。

---

## 一、指令 Directives

### 1. x-data 定义组件数据

**解读**：`x-data` 是 Alpine.js 组件的入口点，它定义了组件的状态数据。所有响应式数据都必须在 `x-data` 中声明。

**示例代码**：

```html
<div x-data="{ count: 0, name: 'Alpine' }">
    <p>计数: <span x-text="count"></span></p>
    <p>名称: <span x-text="name"></span></p>
    <button @click="count++">增加</button>
</div>
```

**适用范围**：
- 页面中任何需要状态管理的区域
- 可以作为根元素包裹整个页面或多个独立组件
- 支持嵌套，子组件可以访问父组件数据（通过 `$parent`）

**注意事项**：
1. `x-data` 的值必须是一个有效的 JavaScript 对象字面量
2. 数据属性可以在 HTML 中直接访问，但方法需要用引号包裹
3. 复杂数据逻辑建议提取到 `<script>` 标签中

```html
<script>
function myComponent() {
    return {
        items: [],
        add(item) {
            this.items.push(item);
        }
    }
}
</script>
<div x-data="myComponent()">
    <!-- 组件内容 -->
</div>
```

---

### 2. x-init 初始化钩子

**解读**：`x-init` 在组件初始化时执行，用于运行一次性设置代码，如数据获取、事件监听初始化等。它是 Alpine 组件生命周期中的重要钩子。

**示例代码**：

```html
<!-- 基本用法：初始化时执行代码 -->
<div x-data="{ count: 0 }" x-init="console.log('组件已初始化')">
    <p>计数: <span x-text="count"></span></p>
    <button @click="count++">增加</button>
</div>

<!-- 初始化时获取数据 -->
<div x-data="{
    posts: [],
    isLoading: true,
    async init() {
        this.isLoading = true;
        try {
            const response = await fetch('https://api.example.com/posts');
            this.posts = await response.json();
        } catch (error) {
            console.error('获取数据失败:', error);
        } finally {
            this.isLoading = false;
        }
    }
}" x-init="init()">
    <div x-show="isLoading">加载中...</div>
    <ul x-show="!isLoading">
        <template x-for="post in posts" :key="post.id">
            <li x-text="post.title"></li>
        </template>
    </ul>
</div>

<!-- 使用 init() 方法（推荐方式） -->
<div x-data="{
    count: 0,
    message: '',
    init() {
        // 初始化逻辑
        this.message = '组件已初始化';
        console.log('init() 方法被调用');

        // 可以访问 $refs, $el 等
        this.$nextTick(() => {
            console.log('DOM 已更新');
        });
    }
}">
    <p x-text="message"></p>
    <p>计数: <span x-text="count"></span></p>
    <button @click="count++">+1</button>
</div>
```

**x-init vs init() 方法**：

| 特性 | x-init | init() 方法 |
|------|--------|-------------|
| 语法 | `x-init="expression"` | 在 x-data 中定义 `init()` 方法 |
| 执行时机 | 组件初始化时 | 组件初始化时 |
| 访问 this | 需要通过 $el 或组件引用 | 直接访问组件数据和方法 |
| 适用场景 | 简单初始化逻辑 | 复杂的初始化逻辑 |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |

**适用范围**：
- 组件挂载时获取初始数据（如从 API 加载数据）
- 初始化第三方库（如图表库、地图库等）
- 设置事件监听器或订阅
- 启动定时器或轮询
- 执行一次性设置代码

**注意事项**：
1. **执行时机**：`x-init` 和 `init()` 在组件初始化时执行，此时 DOM 可能还未完全就绪。如果需要操作 DOM，请使用 `this.$nextTick()`
2. **异步操作**：如果 `init()` 包含异步操作（如数据获取），组件会在异步操作完成前渲染。建议设置加载状态来处理这种情况
3. **错误处理**：在 `init()` 中进行数据获取时，建议添加 try-catch 来处理可能的错误
4. **性能考虑**：避免在 `init()` 中执行昂贵的操作，因为这会阻塞组件的初始渲染
5. **与 x-data 的关系**：`init()` 可以访问 `x-data` 中定义的所有数据和方法，但需要在 `init()` 中使用 `this` 关键字
6. **多次执行**：`init()` 只在组件初始化时执行一次，不会在数据更新时重新执行
7. **嵌套组件**：在嵌套组件中，外层组件的 `init()` 会先执行，然后是内层组件的 `init()`
8. **与 $watch 的配合**：可以在 `init()` 中使用 `this.$watch()` 来设置数据监听器
9. **与 $refs 的配合**：在 `init()` 中访问 `this.$refs` 时，需要使用 `this.$nextTick()` 确保 DOM 已就绪
10. **最佳实践**：对于简单的初始化，可以使用 `x-init`；对于复杂的初始化逻辑，推荐使用 `init()` 方法

---

### 3. x-text 和 x-html 渲染内容

**解读**：这两个指令用于将数据渲染到 DOM 中。`x-text` 以纯文本形式渲染（会转义 HTML），`x-html` 以 HTML 形式渲染（支持富文本）。

**示例代码**：

```html
<div x-data="{
    plainText: '<strong>粗体</strong>',
    richText: '<strong>粗体</strong> 和 <em>斜体</em>'
}">
    <!-- x-text 显示原始HTML代码 -->
    <p x-text="plainText"></p>
    <!-- 输出: &lt;strong&gt;粗体&lt;/strong&gt; -->

    <!-- x-html 渲染为真实HTML -->
    <div x-html="richText"></div>
    <!-- 输出: <strong>粗体</strong> 和 <em>斜体</em> -->
</div>
```

**适用范围**：
- `x-text`：显示用户输入、动态标题、计数器等纯文本内容
- `x-html`：显示富文本编辑器内容、渲染 Markdown 转 HTML 后的内容

**注意事项**：
1. **XSS 安全风险**：使用 `x-html` 时务必确保内容是可信的，不要直接渲染用户输入的 HTML
2. `x-text` 会覆盖元素内的所有子节点
3. 如果需要保留原有内容并追加，使用 `x-html` 配合模板字符串

```html
<!-- 安全的 x-html 使用方式 -->
<div x-data="{ userContent: '' }">
    <!-- 危险：直接渲染用户输入 -->
    <div x-html="userContent"></div>

    <!-- 安全：使用 DOMPurify 等库清理后渲染 -->
    <div x-html="purify(userContent)"></div>
</div>
```

---

### 4. x-show 和 x-if 条件渲染

**解读**：这两个指令控制元素的显示/隐藏。`x-show` 使用 CSS `display` 属性切换，`x-if` 则是真正的条件渲染（元素从 DOM 中增删）。

**示例代码**：

```html
<div x-data="{ isVisible: false, user: null }">
    <!-- x-show: 元素始终存在于 DOM，只是隐藏/显示 -->
    <div x-show="isVisible" x-transition>
        这是一个可切换显示的内容
    </div>
    <button @click="isVisible = !isVisible">切换显示</button>

    <!-- x-if: 条件为真时才渲染到 DOM -->
    <template x-if="user">
        <div>
            <p>用户名: <span x-text="user.name"></span></p>
            <p>邮箱: <span x-text="user.email"></span></p>
        </div>
    </template>

    <template x-if="!user">
        <div>
            <p>请先登录</p>
            <button @click="user = { name: '张三', email: 'zhangsan@example.com' }">登录</button>
        </div>
    </template>
</div>
```

**适用范围**：

| 特性 | x-show | x-if |
|------|--------|------|
| DOM 存在 | 始终存在 | 条件满足才存在 |
| 切换开销 | 小（CSS切换） | 大（DOM操作） |
| 初始渲染开销 | 大（总是渲染） | 小（按需渲染） |
| 适用场景 | 频繁切换的UI | 条件性内容的渲染 |

**注意事项**：
1. `x-if` 必须用在 `<template>` 标签上
2. `x-show` 可以和 `x-transition` 结合使用实现动画效果
3. `x-if` 内部的状态在重新渲染时会重置（除非使用 `x-id` 或外部状态管理）

---

### 5. x-for 列表渲染

**解读**：`x-for` 用于渲染列表数据，类似于 JavaScript 的 `Array.map()`。它必须在 `<template>` 标签上使用。

**示例代码**：

```html
<div x-data="{
    todos: [
        { id: 1, text: '学习 Alpine.js', done: false },
        { id: 2, text: '写代码', done: true },
        { id: 3, text: '部署应用', done: false }
    ],
    newTodo: '',
    addTodo() {
        if (this.newTodo.trim()) {
            this.todos.push({
                id: Date.now(),
                text: this.newTodo,
                done: false
            });
            this.newTodo = '';
        }
    },
    removeTodo(id) {
        this.todos = this.todos.filter(t => t.id !== id);
    }
}">
    <!-- 添加新待办 -->
    <form @submit.prevent="addTodo">
        <input x-model="newTodo" type="text" placeholder="输入新待办...">
        <button type="submit">添加</button>
    </form>

    <!-- 列表渲染 -->
    <ul>
        <template x-for="todo in todos" :key="todo.id">
            <li>
                <input type="checkbox" x-model="todo.done">
                <span x-text="todo.text"
                      :style="todo.done && 'text-decoration: line-through'">
                </span>
                <button @click="removeTodo(todo.id)">删除</button>
            </li>
        </template>
    </ul>

    <!-- 显示统计 -->
    <p>
        已完成: <span x-text="todos.filter(t => t.done).length"></span> /
        总计: <span x-text="todos.length"></span>
    </p>
</div>
```

**高级用法**：

```html
<div x-data="{ items: ['a', 'b', 'c'] }">
    <!-- 获取索引 -->
    <template x-for="(item, index) in items" :key="index">
        <div>
            <span x-text="index + 1"></span>.
            <span x-text="item"></span>
        </div>
    </template>

    <!-- 遍历对象 -->
    <div x-data="{ user: { name: '张三', age: 25, city: '北京' } }">
        <template x-for="(value, key) in user" :key="key">
            <p><strong x-text="key"></strong>: <span x-text="value"></span></p>
        </template>
    </div>
</div>
```

**适用范围**：
- 渲染动态列表（待办事项、商品列表、评论等）
- 需要动态添加/删除/排序的项目
- 表格数据展示

**注意事项**：
1. **必须使用 `:key`**：为每个列表项提供唯一 `key` 是最佳实践，有助于 Alpine 高效更新 DOM
2. `x-for` 必须用在 `<template>` 标签上，实际渲染时会展开模板内容
3. 避免在循环体内直接使用 `x-if`，如有需要应嵌套 `<template>`
4. 修改数组时请使用 Alpine 提供的数组方法，或直接重新赋值以确保响应性

---

### 6. x-on / @ 事件处理

**解读**：`x-on` 用于监听 DOM 事件，执行 JavaScript 表达式。它有简写形式 `@`。

**示例代码**：

```html
<div x-data="{
    count: 0,
    message: ''
}">
    <!-- 基本点击事件 -->
    <button x-on:click="count++">点击 +1</button>

    <!-- 简写形式（推荐） -->
    <button @click="count--">点击 -1</button>

    <!-- 调用方法 -->
    <button @click="reset()">重置</button>

    <!-- 访问事件对象 -->
    <input @input="message = $event.target.value" placeholder="输入内容">

    <!-- 按键修饰符 -->
    <input @keyup.enter="submit()" placeholder="按回车提交">

    <!-- 组合修饰符 -->
    <div @click.ctrl="handleCtrlClick()">Ctrl+点击</div>

    <p>当前计数: <span x-text="count"></span></p>
    <p>输入内容: <span x-text="message"></span></p>
</div>

<script>
function reset() {
    // 访问 Alpine 组件数据
    this.count = 0;
    this.message = '';
}
function submit() {
    alert('提交: ' + this.message);
}
function handleCtrlClick() {
    console.log('Ctrl+点击');
}
</script>
```

**事件修饰符**：

| 修饰符 | 说明 |
|--------|------|
| `.stop` | 阻止事件冒泡 |
| `.prevent` | 阻止默认行为 |
| `.self` | 只在事件源元素触发 |
| `.once` | 只触发一次 |
| `.outside` | 点击元素外部时触发 |

```html
<!-- 阻止表单默认提交 -->
<form @submit.prevent="handleSubmit">
    <button type="submit">提交</button>
</form>

<!-- 阻止冒泡 -->
<div @click="parentClick">
    <button @click.stop="childClick">点击我</button>
</div>

<!-- 点击外部关闭弹窗 -->
<div x-data="{ open: false }">
    <button @click="open = true">打开</button>
    <div x-show="open" @click.outside="open = false">
        点击外部关闭
    </div>
</div>
```

**适用范围**：
- 处理用户交互（点击、输入、滚动等）
- 表单验证和提交
- 实现各种交互模式（模态框、下拉菜单、轮播等）

**注意事项**：
1. 事件处理器可以直接写表达式或调用方法
2. 使用 `$event` 可以访问原生 DOM 事件对象
3. 修饰符可以链式组合使用，如 `@click.stop.prevent`
4. 自定义事件需要使用驼峰命名：`@customEvent` 对应 `dispatchEvent(new CustomEvent('custom-event'))`

---

### 6. x-model 双向数据绑定

**解读**：`x-model` 实现表单元素和数据之间的双向绑定，是处理表单输入的核心指令。

**示例代码**：

```html
<div x-data="{
    text: '',
    checked: false,
    selected: '',
    multiSelect: [],
    radio: 'option1'
}">
    <!-- 文本输入 -->
    <div>
        <label>用户名: <input x-model="text" type="text"></label>
        <p>你输入的是: <span x-text="text"></span></p>
    </div>

    <!-- 复选框 -->
    <div>
        <label>
            <input x-model="checked" type="checkbox">
            同意条款
        </label>
        <p>状态: <span x-text="checked ? '已同意' : '未同意'"></span></p>
    </div>

    <!-- 下拉选择 -->
    <div>
        <label>选择城市:
            <select x-model="selected">
                <option value="">请选择</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="guangzhou">广州</option>
            </select>
        </label>
        <p>选择的城市: <span x-text="selected || '无'"></span></p>
    </div>

    <!-- 多选下拉 -->
    <div>
        <label>多选 (按住 Ctrl 或 Cmd):
            <select x-model="multiSelect" multiple>
                <option value="a">选项 A</option>
                <option value="b">选项 B</option>
                <option value="c">选项 C</option>
            </select>
        </label>
        <p>已选: <span x-text="JSON.stringify(multiSelect)"></span></p>
    </div>

    <!-- 单选按钮 -->
    <div>
        <label><input x-model="radio" type="radio" value="option1"> 选项 1</label>
        <label><input x-model="radio" type="radio" value="option2"> 选项 2</label>
        <label><input x-model="radio" type="radio" value="option3"> 选项 3</label>
        <p>选中: <span x-text="radio"></span></p>
    </div>
</div>
```

**修饰符**：

| 修饰符 | 说明 | 示例 |
|--------|------|------|
| `.lazy` | 失焦时更新而非实时 | `x-model.lazy="text"` |
| `.number` | 转换为数字类型 | `x-model.number="age"` |
| `.debounce` | 防抖处理 | `x-model.debounce="search"` |
| `.debounce.500ms` | 指定防抖时间 | `x-model.debounce.500ms="search"` |

```html
<div x-data="{
    lazyText: '',
    numberInput: '',
    searchQuery: ''
}">
    <!-- lazy - 输入完成后失焦才更新 -->
    <input x-model.lazy="lazyText" placeholder="输入后点击外部">
    <p>懒加载值: <span x-text="lazyText"></span></p>

    <!-- number - 自动转换为数字 -->
    <input x-model.number="numberInput" type="number" placeholder="输入数字">
    <p>类型: <span x-text="typeof numberInput"></span>, 值: <span x-text="numberInput"></span></p>

    <!-- debounce - 防抖，避免频繁触发 -->
    <input x-model.debounce.500ms="searchQuery" placeholder="搜索（防抖500ms）">
    <p>搜索: <span x-text="searchQuery"></span></p>
</div>
```

**适用范围**：
- 所有表单输入元素（input、textarea、select）
- 自定义表单组件
- 搜索框实时查询
- 表单验证

**注意事项**：
1. `x-model` 优先级高于原生 `value` 属性
2. 复选框绑定数组时，value 决定数组元素值
3. 单选/复选框使用 `.number` 修饰符时，value 会被转为数字
4. 自定义组件可以通过 `$dispatch` 和自定义事件配合 `x-model` 使用

---

### 8. x-modelable 暴露组件内部状态

**解读**：`x-modelable` 允许组件内部的状态被外部通过 `x-model` 双向绑定。它是创建可复用表单组件的关键指令，让自定义组件也能像原生表单元素一样使用 `x-model`。

**示例代码**：

```html
<!-- 自定义计数器组件 -->
<div x-data="{ count: 0 }" x-modelable="count" class="flex items-center gap-2">
    <button @click="count--" class="px-3 py-1 bg-gray-200 rounded">-</button>
    <span x-text="count" class="w-12 text-center"></span>
    <button @click="count++" class="px-3 py-1 bg-gray-200 rounded">+</button>
</div>

<!-- 使用自定义组件 -->
<div x-data="{ total: 5 }">
    <p>当前值: <span x-text="total"></span></p>
    <div x-data="{ count: $parent.total }" x-modelable="count" @input="$parent.total = count">
        <button @click="count--">-</button>
        <span x-text="count"></span>
        <button @click="count++">+</button>
    </div>
</div>

<!-- 自定义开关组件 -->
<div x-data="{ isDark: false }" class="p-4">
    <p class="mb-2">主题模式: <span x-text="isDark ? '暗黑' : '明亮'"></span></p>

    <!-- 可复用的 Toggle 组件 -->
    <div x-data="{ on: false }" x-modelable="on"
         class="relative inline-flex h-6 w-11 cursor-pointer rounded-full transition-colors duration-200"
         :class="on ? 'bg-blue-600' : 'bg-gray-200'"
         @click="on = !on">
        <span class="sr-only">切换</span>
        <span class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200"
              :class="on ? 'translate-x-5' : 'translate-x-0'">
        </span>
    </div>

    <div class="mt-4" x-data="{ on: $parent.isDark }" x-modelable="on" @input="$parent.isDark = on">
        <div x-data="{ on: false }" x-modelable="on"
             class="relative inline-flex h-6 w-11 cursor-pointer rounded-full transition-colors duration-200"
             :class="on ? 'bg-blue-600' : 'bg-gray-200'"
             @click="on = !on">
            <span class="sr-only">暗黑模式</span>
            <span class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200"
                  :class="on ? 'translate-x-5' : 'translate-x-0'">
            </span>
        </div>
        <span class="ml-2 text-sm text-gray-600">暗黑模式</span>
    </div>
</div>
```

**适用范围**：
- 创建可复用的表单组件（自定义输入框、选择器、开关等）
- 封装复杂的 UI 组件逻辑
- 实现父子组件间的双向数据绑定
- 构建组件库时提供类似原生表单元素的使用体验

**注意事项**：
1. `x-modelable` 声明的属性名必须存在于组件的 `x-data` 中
2. 外部通过 `x-model` 绑定的变量名可以与内部不同
3. `x-modelable` 可以与 `x-model` 修饰符一起使用（如 `.number`、`.lazy`）
4. 多个 `x-modelable` 可以在一个组件中声明，但需要配合不同的命名空间
5. 事件冒泡：内部状态变化时，组件会派发 `input` 事件通知父组件

---

### 9. x-effect 响应式副作用

**解读**：`x-effect` 用于执行响应式副作用，当依赖的响应式数据变化时自动重新执行。它类似于 Vue 的 `watchEffect`，适合执行不需要显式监听的数据副作用。

**示例代码**：

```html
<div x-data="{
    count: 0,
    message: '',
    log: [],

    addLog(action) {
        const time = new Date().toLocaleTimeString();
        this.log.unshift({ time, action });
        if (this.log.length > 10) this.log.pop();
    }
}">
    <!-- x-effect 自动追踪依赖 -->
    <div x-effect="message = '计数器当前值为: ' + count; addLog('count 变为 ' + count)"></div>

    <p>当前值: <span x-text="count"></span></p>
    <p x-text="message"></p>

    <button @click="count++">+1</button>
    <button @click="count--">-1</button>
    <button @click="count = 0">重置</button>

    <h4>操作日志:</h4>
    <ul>
        <template x-for="entry in log" :key="entry.time">
            <li><span x-text="entry.time"></span>: <span x-text="entry.action"></span></li>
        </template>
    </ul>
</div>

<!-- 使用 x-effect 实现防抖保存 -->
<div x-data="{
    content: localStorage.getItem('draft') || '',
    saveStatus: '已保存',

    save() {
        localStorage.setItem('draft', this.content);
        this.saveStatus = '已保存';
    }
}">
    <h3>自动保存草稿</h3>
    <p>状态: <span x-text="saveStatus"></span></p>

    <textarea
        x-model="content"
        @input="saveStatus = '未保存'"
        placeholder="输入内容..."
        rows="6"
        cols="40">
    </textarea>

    <!-- 使用 x-effect 实现防抖保存 -->
    <div x-effect="content; saveStatus = '保存中...'; setTimeout(() => save(), 1000)"></div>
</div>

<!-- x-effect 与 DOM 操作 -->
<div x-data="{
    items: [],
    highlightIndex: -1
}">
    <h3>高亮显示</h3>
    <button @click="items.push('项目 ' + (items.length + 1)); highlightIndex = items.length - 1">添加</button>
    <button @click="items.pop(); highlightIndex = -1">移除</button>

    <ul>
        <template x-for="(item, index) in items" :key="index">
            <li
                x-text="item"
                :style="index === highlightIndex ? 'background: yellow; transition: background 0.5s' : ''">
            </li>
        </template>
    </ul>

    <!-- 自动清除高亮 -->
    <div x-effect="highlightIndex; if (highlightIndex >= 0) { setTimeout(() => highlightIndex = -1, 2000) }"></div>
</div>
```

**适用范围**：
- 自动执行依赖于响应式数据的副作用
- 实现防抖/节流功能
- 自动同步数据到 localStorage 或服务器
- 根据状态变化自动触发 DOM 操作或动画
- 实现复杂的联动效果

**注意事项**：
1. `x-effect` 会在初始化时立即执行一次，然后每当依赖的响应式数据变化时重新执行
2. `x-effect` 会自动追踪表达式中访问的响应式属性作为依赖
3. 避免在 `x-effect` 中执行昂贵的操作，以免频繁触发影响性能
4. 如果需要在组件卸载时清理副作用，使用 `$cleanup` 或手动管理
5. 与 `$watch` 的区别：`x-effect` 自动追踪依赖，而 `$watch` 需要显式指定监听属性
6. 可以在 `x-effect` 中使用异步操作，但要小心内存泄漏问题

---

### 10. x-id 生成唯一标识符

**解读**：`x-id` 用于为元素生成唯一的 ID 标识符，主要用于解决可访问性（Accessibility）需求，如正确关联 `label` 和 `input` 的 `for` 和 `id` 属性，或关联 `aria-describedby` 等 ARIA 属性。它确保在页面中有多个相同组件实例时，ID 不会冲突。

**示例代码**：

```html
<!-- 基本用法：关联 label 和 input -->
<div x-data="{ email: '' }" x-id="['email-input']">
    <label :for="$id('email-input')">邮箱地址</label>
    <input :id="$id('email-input')" type="email" x-model="email">
</div>

<!-- 多个 ID 声明 -->
<div x-data="{
    username: '',
    password: '',
    error: ''
}" x-id="['username', 'password', 'error']">
    <div>
        <label :for="$id('username')">用户名</label>
        <input
            :id="$id('username')"
            type="text"
            x-model="username"
            :aria-describedby="error ? $id('error') : null"
            :aria-invalid="error ? 'true' : 'false'"
        >
    </div>

    <div>
        <label :for="$id('password')">密码</label>
        <input
            :id="$id('password')"
            type="password"
            x-model="password"
            :aria-describedby="error ? $id('error') : null"
        >
    </div>

    <div x-show="error" :id="$id('error')" role="alert" class="error">
        <span x-text="error"></span>
    </div>
</div>
```

**可复用表单组件示例**：

```html
<!-- TextInput 组件 -->
<div x-data="{
    value: '',
    label: '字段名',
    type: 'text',
    placeholder: '',
    required: false,
    error: '',
    validate() {
        if (this.required && !this.value.trim()) {
            this.error = `${this.label}不能为空`;
        } else {
            this.error = '';
        }
    }
}" x-id="['input', 'label', 'error']"
class="form-field">
    <label :for="$id('input')" :id="$id('label')" class="form-label">
        <span x-text="label"></span>
        <span x-show="required" class="required-mark">*</span>
    </label>

    <input
        :type="type"
        :id="$id('input')"
        :placeholder="placeholder"
        :required="required"
        :aria-required="required"
        :aria-invalid="error ? 'true' : 'false'"
        :aria-labelledby="$id('label')"
        :aria-describedby="error ? $id('error') : null"
        x-model="value"
        @blur="validate()"
        class="form-input"
        :class="{ 'has-error': error }"
    >

    <span
        x-show="error"
        :id="$id('error')"
        x-text="error"
        role="alert"
        class="error-message"
    ></span>
</div>
```

**适用范围**：
- 表单可访问性：关联 `label` 的 `for` 和 `input` 的 `id`
- ARIA 属性关联：`aria-labelledby`、`aria-describedby`、`aria-controls`、`aria-expanded` 等
- 复用组件时确保 ID 唯一性，避免冲突
- 列表项中需要唯一标识时
- 实现符合 WCAG 标准的无障碍访问
- 复杂交互组件（如 Tabs、Accordion、Modal）的正确语义化

**注意事项**：
1. **使用 `$id()` 函数**：在需要 ID 的地方使用 `$id('suffix')`，Alpine 会自动生成格式为 `alpine-{unique}-{suffix}` 的唯一 ID。调用时不需要加前缀，直接使用声明的后缀名
2. **声明 x-id 数组**：在组件根元素上使用 `x-id="['suffix1', 'suffix2']"` 声明需要的 ID 后缀，这是最佳实践，能确保 ID 正确生成
3. **作用域隔离**：每个 Alpine 组件的 ID 空间是独立的，嵌套组件各自维护自己的 ID 生成。子组件可以重新定义相同的 suffix 而不会与父组件冲突
4. **列表中的使用**：在 `x-for` 循环中使用 `x-id` 时，建议在循环项上单独声明 `x-data` 和 `x-id`，以确保每个列表项的 ID 唯一。例如：`x-data x-id="['item']"`
5. **性能考虑**：`$id()` 是轻量操作，不会显著影响性能，可以在模板中多次调用。每次调用返回相同的结果，不会产生重复 ID
6. **与手动 ID 对比**：相比手动硬编码 ID，`x-id` 避免了组件复用时的冲突，特别推荐在可复用组件中使用。手动 ID 在单页面应用中容易导致可访问性问题
7. **ARIA 合规性**：使用 `x-id` 可以正确实现 ARIA 属性的关联，有助于通过可访问性审核和屏幕阅读器测试。这对于企业级应用和政府网站尤为重要
8. **调试技巧**：可以通过查看生成的 HTML 来验证 `$id()` 的输出格式，通常是 `alpine-xxx-suffix` 的形式。在开发阶段，建议开启浏览器的开发者工具检查元素
9. **命名约定**：建议使用有意义的 suffix 名称，如 `'input'`、`'label'`、`'error'` 等，以便于理解和维护。避免使用过于通用的名称如 `'id'` 或 `'el'`
10. **与 x-teleport 配合**：当使用 `x-teleport` 时，`$id()` 生成的 ID 仍然有效，可以在传送后的元素上使用。确保在 `x-teleport` 的模板内容中继续使用 `$id()` 调用

---

### 11. x-ignore 跳过编译

**解读**：`x-ignore` 用于告诉 Alpine.js 跳过对特定元素及其子元素的编译。这在需要与 Alpine 之外的库（如 Markdown 渲染器、代码高亮库）配合使用时非常有用。

**示例代码**：

```html
<div x-data="{ message: 'Hello Alpine!' }">
    <p>Alpine 管理的内容: <span x-text="message"></span></p>

    <!-- x-ignore 阻止 Alpine 编译此区域 -->
    <div x-ignore>
        <p>这段内容会被 Alpine 忽略</p>
        <p>即使包含 {{ 双大括号 }} 也不会被解析</p>
        <p>原生 HTML: <span x-text="message">这里的指令无效</span></p>

        <!-- 可以放置其他库的代码 -->
        <pre><code class="language-javascript">
// 代码高亮库（如 Prism.js）可以安全地处理这里
const x = 1;
console.log(x); // Alpine 不会解析这里的指令
        </code></pre>
    </div>
</div>

<!-- 与 Markdown 渲染器配合 -->
<div x-data="{ content: '# Hello\\n\\nThis is **markdown** with {{ alpine }} expressions.' }">
    <h3>原始 Markdown:</h3>
    <pre x-text="content"></pre>

    <h3>渲染的 Markdown (使用 x-ignore 避免 Alpine 解析):</h3>
    <div x-ignore x-html="marked.parse(content)">
        <!-- Markdown 渲染器可以安全地输出 HTML -->
        <!-- Alpine 不会处理这里的 {{ }} 表达式 -->
    </div>
</div>

<!-- 与 Vue 或其他框架共存 -->
<div x-data="{ alpineData: '来自 Alpine' }">
    <p>Alpine 数据: <span x-text="alpineData"></span></p>

    <!-- 让 Vue 管理这个区域 -->
    <div x-ignore id="vue-app">
        <p>Vue 管理的内容: {{ vueData }}</p>
        <button @click="vueMethod">Vue 按钮</button>
    </div>
</div>

<script>
// Vue 可以安全地初始化，不会被 Alpine 干扰
new Vue({
    el: '#vue-app',
    data: { vueData: '来自 Vue' },
    methods: {
        vueMethod() {
            alert('这是 Vue 的方法');
        }
    }
});
</script>
```

**适用范围**：
- 需要渲染来自用户输入的富文本（Markdown、HTML 等）时，防止 Alpine 解析其中的 `{{ }}` 表达式
- 与代码高亮库（Prism.js、highlight.js）配合，避免 Alpine 处理代码块中的指令
- 在同一个页面中与其他前端框架（Vue、React 等）共存
- 使用服务器端模板引擎（如 Jinja2、Blade）时，避免与 Alpine 的语法冲突
- 嵌入第三方小组件（天气、地图、广告等），确保其代码不被 Alpine 干扰

**注意事项**：
1. `x-ignore` 会阻止 Alpine 编译该元素及其所有子元素，这意味着内部的任何 Alpine 指令（如 `x-data`、`x-text`、`@click` 等）都不会生效
2. `x-ignore` 只影响 Alpine 的编译过程，不会修改 DOM 或样式，元素仍然可见并可以包含其他非 Alpine 的功能
3. 如果需要在 `x-ignore` 区域内使用某些 Alpine 功能，可以将该区域拆分为多个独立的元素，只对需要忽略的部分使用 `x-ignore`
4. `x-ignore` 可以用于性能优化，对于大型的静态内容区域，使用 `x-ignore` 可以避免 Alpine 不必要的遍历和编译
5. 与 `x-cloak` 不同，`x-ignore` 是永久性的忽略，而不是等待 Alpine 初始化完成后的显示
6. 如果 `x-ignore` 包裹的元素内部包含嵌套的 Alpine 组件，这些组件也不会被初始化

---

### 12. x-cloak 隐藏未编译内容

**解读**：`x-cloak` 用于在 Alpine.js 完成初始化之前隐藏元素，防止用户看到未编译的模板（如 `{{ }}` 表达式或隐藏的 `x-show` 元素）。它常与 CSS 规则 `[x-cloak] { display: none !important; }` 配合使用。

**示例代码**：

```html
<!-- 必须在页面样式中定义 x-cloak 的隐藏规则 -->
<style>
    /* 基本隐藏规则 */
    [x-cloak] {
        display: none !important;
    }

    /* 可选：添加过渡效果，使显示更平滑 */
    [x-cloak].fade-in {
        opacity: 0;
        transition: opacity 0.3s ease;
    }

    [x-cloak].fade-in.visible {
        opacity: 1;
    }
</style>

<!-- 基本用法：防止 {{ }} 表达式闪烁 -->
<div x-data="{ message: 'Hello Alpine!' }" x-cloak>
    <p>页面加载完成后才会显示以下内容：</p>
    <p>Message: {{ message }}</p>
    <p>时间戳: {{ new Date().toLocaleString() }}</p>
</div>

<!-- 配合 x-show 使用，防止隐藏元素闪烁 -->
<div x-data="{ isOpen: false }" x-cloak>
    <button @click="isOpen = !isOpen">切换面板</button>

    <!-- 初始状态为隐藏，如果不加 x-cloak，可能会先显示再隐藏 -->
    <div x-show="isOpen" x-transition class="panel">
        <p>这个面板的内容在 Alpine 初始化前是隐藏的</p>
    </div>
</div>

<!-- 表单防止闪烁 -->
<div x-data="{
    form: { name: '', email: '' },
    errors: {},
    isSubmitting: false,
    successMessage: ''
}" x-cloak>
    <form @submit.prevent="submitForm">
        <div>
            <label>姓名:</label>
            <input x-model="form.name" type="text">
            <span x-show="errors.name" x-text="errors.name" class="error"></span>
        </div>

        <div>
            <label>邮箱:</label>
            <input x-model="form.email" type="email">
            <span x-show="errors.email" x-text="errors.email" class="error"></span>
        </div>

        <button type="submit" :disabled="isSubmitting">
            <span x-show="!isSubmitting">提交</span>
            <span x-show="isSubmitting">提交中...</span>
        </button>

        <div x-show="successMessage" x-text="successMessage" class="success"></div>
    </form>
</div>

<!-- 在复杂的列表中使用 x-cloak -->
<div x-data="{
    items: [],
    isLoading: true
}" x-init="loadItems()" x-cloak>
    <h3>项目列表</h3>

    <!-- 加载状态 -->
    <div x-show="isLoading">加载中...</div>

    <!-- 列表内容 -->
    <ul x-show="!isLoading">
        <template x-for="item in items" :key="item.id">
            <li x-text="item.name"></li>
        </template>
    </ul>
</div>

<script>
async function loadItems() {
    // 模拟 API 调用
    await new Promise(resolve => setTimeout(resolve, 1000));
    this.items = [
        { id: 1, name: '项目 1' },
        { id: 2, name: '项目 2' },
        { id: 3, name: '项目 3' }
    ];
    this.isLoading = false;
}

async function submitForm() {
    this.isSubmitting = true;
    this.errors = {};
    this.successMessage = '';

    // 模拟提交
    await new Promise(resolve => setTimeout(resolve, 1000));

    this.isSubmitting = false;
    this.successMessage = '提交成功！';
}
</script>
```

**适用范围**：
- 防止页面加载时显示未编译的 `{{ }}` 模板表达式
- 避免 `x-show="false"` 的元素在 Alpine 初始化完成前闪烁显示
- 在复杂的表单或列表中提供更平滑的加载体验
- 配合 CSS 过渡效果实现优雅的显示动画
- 在服务器端渲染（SSR）与客户端 Alpine 初始化的过渡期间隐藏内容

**注意事项**：
1. **必须配合 CSS 使用**：`x-cloak` 本身只是标记属性，必须在 CSS 中定义 `[x-cloak] { display: none !important; }` 才能生效
2. **移除时机**：Alpine 初始化完成后会自动移除元素上的 `x-cloak` 属性，使元素显示
3. **优先级问题**：使用 `!important` 确保 `x-cloak` 的隐藏规则不会被其他样式覆盖
4. **与 x-show 的区别**：`x-cloak` 只在初始化前生效，而 `x-show` 是响应式的状态控制
5. **过渡效果**：可以配合 CSS 过渡实现平滑显示，但要注意 `[x-cloak]` 状态下的 `opacity: 0` 可能不会触发动画
6. **性能考虑**：对于大量元素，每个都添加 `x-cloak` 可能会略微影响初始化性能，但通常可以忽略不计
7. **服务器端渲染**：在使用 SSR 时，确保服务器输出的 HTML 中包含 `x-cloak` 属性和对应的 CSS 规则

---

### 13. x-teleport 传送元素

**解读**：`x-teleport` 允许将元素移动到 DOM 中的其他位置，通常用于将模态框、下拉菜单、工具提示等元素移动到 `body` 标签下，以避免父元素的 CSS 约束（如 `overflow: hidden` 或 `z-index` 层级问题）。

**示例代码**：

```html
<!-- 基本用法：将内容传送到 body 末尾 -->
<div x-data="{ open: false }">
    <button @click="open = !open">打开模态框</button>

    <template x-teleport="body">
        <div x-show="open"
             x-transition
             class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
             @click="open = false">
            <div class="bg-white p-6 rounded-lg max-w-md w-full" @click.stop>
                <h2 class="text-xl font-bold mb-4">模态框标题</h2>
                <p>这个模态框被传送到了 body 标签下，不受父元素样式影响。</p>
                <button @click="open = false" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded">关闭</button>
            </div>
        </div>
    </template>
</div>

<!-- 传送到指定选择器 -->
<div x-data="{ showTooltip: false }">
    <span
        @mouseenter="showTooltip = true"
        @mouseleave="showTooltip = false"
        class="cursor-help border-b border-dotted border-gray-500">
        悬停查看提示
    </span>

    <template x-teleport="#tooltip-container">
        <div x-show="showTooltip"
             x-transition:enter="transition ease-out duration-200"
             x-transition:enter-start="opacity-0 translate-y-1"
             x-transition:enter-end="opacity-100 translate-y-0"
             class="absolute z-50 px-2 py-1 text-sm text-white bg-gray-800 rounded shadow-lg">
            这是工具提示内容
        </div>
    </template>
</div>

<!-- 专门放置传送内容的容器 -->
<div id="tooltip-container" class="relative"></div>

<!-- 下拉菜单传送 -->
<div x-data="{ open: false, items: ['选项 1', '选项 2', '选项 3'] }" class="relative inline-block">
    <button @click="open = !open" class="px-4 py-2 bg-gray-200 rounded">
        选择选项
        <span x-show="!open">▼</span>
        <span x-show="open">▲</span>
    </button>

    <template x-teleport="body">
        <div x-show="open"
             @click.outside="open = false"
             x-transition:enter="transition ease-out duration-100"
             x-transition:enter-start="opacity-0 scale-95"
             x-transition:enter-end="opacity-100 scale-100"
             x-transition:leave="transition ease-in duration-75"
             x-transition:leave-start="opacity-100 scale-100"
             x-transition:leave-end="opacity-0 scale-95"
             class="absolute z-50 w-48 mt-1 bg-white border rounded shadow-lg">
            <template x-for="(item, index) in items" :key="index">
                <a href="#"
                   @click.prevent="open = false; alert('选择了: ' + item)"
                   class="block px-4 py-2 text-gray-700 hover:bg-gray-100"
                   :class="{ 'border-t': index > 0 }"
                   x-text="item">
                </a>
            </template>
        </div>
    </template>
</div>

<!-- 多个传送共存 -->
<div x-data="{ modal1: false, modal2: false }">
    <button @click="modal1 = true">打开模态框 1</button>
    <button @click="modal2 = true">打开模态框 2</button>

    <template x-teleport="body">
        <div x-show="modal1"
             x-transition
             class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
            <div class="bg-white p-6 rounded-lg">
                <h2>模态框 1</h2>
                <button @click="modal1 = false">关闭</button>
            </div>
        </div>
    </template>

    <template x-teleport="body">
        <div x-show="modal2"
             x-transition
             class="fixed inset-0 z-50 flex items-center justify-center bg-red-900 bg-opacity-50">
            <div class="bg-white p-6 rounded-lg">
                <h2>模态框 2</h2>
                <button @click="modal2 = false">关闭</button>
            </div>
        </div>
    </template>
</div>
```

**适用范围**：
- 模态框/对话框需要跳出父元素的 CSS 约束（如 `overflow: hidden`）
- 下拉菜单、工具提示需要正确的 `z-index` 层级
- 需要在 `body` 级别渲染遮罩层，覆盖整个页面
- 父元素有 `transform`、`perspective`、`filter` 等属性创建了新的包含块时
- 需要在不同的 DOM 分支间共享状态，但元素位置需要独立

**注意事项**：
1. **必须使用 `<template>` 标签**：`x-teleport` 只能用在 `<template>` 标签上，实际传送的是 `<template>` 的内容
2. **目标选择器**：`x-teleport` 的值是一个 CSS 选择器（如 `"body"`、`"#my-container"`、`".modal-wrapper"`），目标元素必须存在于 DOM 中
3. **状态保持**：传送的元素保持其 Alpine 组件的响应式状态，可以访问原 `x-data` 中的所有数据和方法
4. **事件冒泡**：在传送后的元素上触发的事件仍然会冒泡到原组件
5. **生命周期**：当原组件被销毁时，传送的元素也会被移除
6. **多个传送**：同一个组件可以有多个 `x-teleport`，它们都共享同一个状态
7. **嵌套传送**：传送的元素内部还可以有另一个 `x-teleport`，实现嵌套传送
8. **样式隔离**：传送的元素脱离了原父元素的样式上下文，可能需要重新定义样式或使用内联样式
9. **可访问性**：传送模态框等组件时，记得管理焦点（可以使用 `@alpinejs/focus` 插件）
10. **性能考虑**：频繁创建和销毁传送的元素可能影响性能，对于复杂的场景考虑使用 `x-show` 而不是销毁重建

---

### 14. x-bind / : 属性绑定

**解读**：`x-bind` 用于动态绑定 HTML 属性，简写形式为 `:`。可以绑定单个属性，也可以使用对象语法批量绑定。

**示例代码**：

```html
<div x-data="{
    imageUrl: 'https://via.placeholder.com/150',
    altText: '示例图片',
    isActive: true,
    isDisabled: false,
    inputType: 'text',
    customClasses: 'btn btn-primary'
}">
    <!-- 基本属性绑定 -->
    <img :src="imageUrl" :alt="altText">

    <!-- 绑定布尔属性 -->
    <button :disabled="isDisabled">提交</button>
    <input :type="inputType" placeholder="动态类型输入框">

    <!-- 类名绑定 - 对象语法 -->
    <div :class="{ active: isActive, disabled: isDisabled }">
        根据状态动态切换类名
    </div>

    <!-- 类名绑定 - 数组语法 -->
    <div :class="[customClasses, isActive ? 'active' : '']">
        组合多个类名
    </div>

    <!-- 样式绑定 -->
    <div :style="{ color: isActive ? 'green' : 'red', fontSize: '18px' }">
        动态内联样式
    </div>

    <!-- 批量属性绑定 -->
    <input x-bind="{
        type: 'email',
        placeholder: '输入邮箱',
        disabled: isDisabled
    }">

    <!-- 切换按钮 -->
    <button @click="isActive = !isActive">
        切换状态 (当前: <span x-text="isActive ? '激活' : '未激活'"></span>)
    </button>
</div>
```

**适用范围**：
- 动态图片地址 `src`、链接 `href`
- 条件性地添加/移除 CSS 类
- 动态设置内联样式
- 控制表单元素的 `disabled`、`readonly` 等状态
- 批量绑定多个属性

**注意事项**：
1. 绑定 `class` 时，对象语法中值为真值的类名会被添加
2. 绑定 `style` 时，CSS 属性名可以用驼峰或短横线（需要引号）
3. 布尔属性（如 `disabled`、`checked`）绑定假值时会被移除
4. 批量绑定时，属性名作为对象键，属性值作为绑定值

```html
<!-- class 绑定的更多示例 -->
<div x-data="{ hasError: true, isImportant: false }">
    <!-- 多个类名条件 -->
    <div :class="{ 'text-red': hasError, 'font-bold': isImportant }">
        错误信息
    </div>

    <!-- 三元表达式 -->
    <div :class="hasError ? 'bg-red-100' : 'bg-green-100'">
        状态面板
    </div>
</div>
```

---

### 15. x-ref 引用元素

**解读**：`x-ref` 用于给元素添加引用标识，然后通过 `$refs` 对象在 JavaScript 中直接访问该元素，类似于原生 JS 的 `getElementById`。

**示例代码**：

```html
<div x-data="{
    focusInput() {
        this.$refs.myInput.focus();
    },
    getInputValue() {
        alert(this.$refs.myInput.value);
    },
    clearInput() {
        this.$refs.myInput.value = '';
        this.$refs.myInput.focus();
    },
    scrollToBottom() {
        this.$refs.chatContainer.scrollTop = this.$refs.chatContainer.scrollHeight;
    }
}">
    <!-- 基本引用 -->
    <input x-ref="myInput" type="text" placeholder="输入内容">

    <button @click="focusInput()">聚焦输入框</button>
    <button @click="getInputValue()">获取值</button>
    <button @click="clearInput()">清空</button>

    <!-- 引用容器 -->
    <div x-ref="chatContainer" style="height: 200px; overflow-y: auto; border: 1px solid #ccc;">
        <p>聊天消息...</p>
        <p>更多消息...</p>
        <p>最新消息...</p>
    </div>
    <button @click="scrollToBottom()">滚动到底部</button>

    <!-- 引用多个元素 -->
    <template x-for="i in 3">
        <input x-ref="inputs" :id="'input-' + i" type="text" :placeholder="'输入 ' + i">
    </template>
    <button @click="$refs.inputs[0].focus()">聚焦第一个</button>
</div>
```

**适用范围**：
- 需要直接操作 DOM 元素（聚焦、滚动、获取尺寸等）
- 与第三方库集成时需要获取元素引用
- 操作 `<canvas>`、`<video>` 等多媒体元素
- 表单验证时获取原生表单元素

**注意事项**：
1. `$refs` 只在组件挂载后才可用，避免在 `x-data` 初始化时访问
2. 在 `x-for` 中使用 `x-ref` 会创建数组，可以通过索引访问 `$refs.items[0]`
3. 引用名称必须是合法的 JavaScript 标识符
4. 动态 `x-ref`（如 `:x-ref="'ref-' + id"`）在 Alpine 3.13+ 支持

```html
<!-- 实际应用场景：文件上传 -->
<div x-data="{
    handleFileSelect() {
        const file = this.$refs.fileInput.files[0];
        if (file) {
            console.log('文件名:', file.name);
            console.log('文件大小:', file.size);
            // 执行上传逻辑...
        }
    }
}">
    <input x-ref="fileInput"
           type="file"
           @change="handleFileSelect()"
           style="display: none;">
    <button @click="$refs.fileInput.click()">选择文件</button>
</div>
```

---

### 16. x-transition 过渡动画

**解读**：`x-transition` 用于在元素显示/隐藏时添加过渡动画。它可以与 `x-show` 和 `x-if` 配合使用。

**示例代码**：

```html
<div x-data="{ open: false, items: ['Item 1', 'Item 2', 'Item 3'] }">
    <button @click="open = !open">切换面板</button>

    <!-- 基本过渡 -->
    <div x-show="open" x-transition>
        这个面板有默认的淡入淡出效果
    </div>

    <!-- 自定义过渡类 -->
    <div x-show="open"
         x-transition:enter="transition ease-out duration-300"
         x-transition:enter-start="opacity-0 transform scale-95"
         x-transition:enter-end="opacity-100 transform scale-100"
         x-transition:leave="transition ease-in duration-200"
         x-transition:leave-start="opacity-100 transform scale-100"
         x-transition:leave-end="opacity-0 transform scale-95">
        自定义缩放动画
    </div>

    <!-- 列表动画 -->
    <template x-for="(item, index) in items" :key="index">
        <div x-show="open"
             x-transition:enter="transition ease-out duration-300"
             x-transition:enter-start="opacity-0 transform -translate-x-full"
             x-transition:enter-end="opacity-100 transform translate-x-0"
             :style="`transition-delay: ${index * 100}ms`"
             x-text="item">
        </div>
    </template>
</div>
```

**过渡修饰符**：

| 修饰符 | 说明 |
|--------|------|
| `.opacity` | 添加透明度渐变 |
| `.scale` | 添加缩放效果 |
| `.duration.XXXms` | 设置持续时间 |
| `.delay.XXXms` | 设置延迟 |
| `.origin.{position}` | 设置变换原点 |

```html
<!-- 使用修饰符简化过渡 -->
<div x-show="open"
     x-transition.opacity.duration.500ms
     class="p-4 bg-blue-100">
    淡入淡出，持续500ms
</div>

<div x-show="open"
     x-transition.scale.origin.top.duration.300ms
     class="p-4 bg-green-100">
    从顶部缩放展开
</div>
```

**适用范围**：
- 显示/隐藏面板、弹窗、下拉菜单
- 列表项的添加/删除动画
- 页面切换过渡
- 手风琴效果

**注意事项**：
1. `x-transition` 必须与 `x-show` 或 `x-if` 一起使用
2. 自定义过渡类需要配合 Tailwind CSS 或其他 CSS 框架使用
3. 列表动画需要为每个项目设置 `:key` 和适当的延迟
4. 复杂动画建议使用 CSS `@keyframes` 配合 `x-transition` 类

```html
<!-- 完整示例：模态框动画 -->
<div x-data="{ isOpen: false }" class="relative">
    <button @click="isOpen = true" class="btn btn-primary">打开模态框</button>

    <!-- 背景遮罩 -->
    <div x-show="isOpen"
         x-transition:enter="transition-opacity ease-linear duration-300"
         x-transition:enter-start="opacity-0"
         x-transition:enter-end="opacity-100"
         x-transition:leave="transition-opacity ease-linear duration-300"
         x-transition:leave-start="opacity-100"
         x-transition:leave-end="opacity-0"
         @click="isOpen = false"
         class="fixed inset-0 bg-black bg-opacity-50 z-40">
    </div>

    <!-- 模态框内容 -->
    <div x-show="isOpen"
         x-transition:enter="transition ease-out duration-300"
         x-transition:enter-start="opacity-0 transform scale-95 translate-y-4"
         x-transition:enter-end="opacity-100 transform scale-100 translate-y-0"
         x-transition:leave="transition ease-in duration-200"
         x-transition:leave-start="opacity-100 transform scale-100 translate-y-0"
         x-transition:leave-end="opacity-0 transform scale-95 translate-y-4"
         @click.outside="isOpen = false"
         class="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none">
        <div class="bg-white rounded-lg shadow-xl p-6 max-w-md w-full pointer-events-auto">
            <h2 class="text-xl font-bold mb-4">模态框标题</h2>
            <p class="mb-4">这是模态框的内容。</p>
            <div class="flex justify-end gap-2">
                <button @click="isOpen = false" class="btn">取消</button>
                <button @click="isOpen = false" class="btn btn-primary">确认</button>
            </div>
        </div>
    </div>
</div>
```
