生命周期 #

概述 #

Alpine.js 组件从创建到销毁经历一系列阶段,称为生命周期。理解生命周期可以帮助你在正确的时机执行代码。

生命周期阶段 #

text
创建 → 初始化 → 挂载 → 更新 → 销毁

init 钩子 #

基本用法 #

init 在组件初始化时执行:

html
<div x-data="{
    message: '',
    init() {
        this.message = 'Hello!'
        console.log('组件已初始化')
    }
}">
    <p x-text="message"></p>
</div>

加载数据 #

html
<div x-data="{
    users: [],
    loading: false,
    async init() {
        await this.loadUsers()
    },
    async loadUsers() {
        this.loading = true
        const res = await fetch('/api/users')
        this.users = await res.json()
        this.loading = false
    }
}">
    <div x-show="loading">加载中...</div>
    <template x-for="user in users">
        <p x-text="user.name"></p>
    </template>
</div>

设置监听器 #

html
<div x-data="{
    scrollY: 0,
    onScroll() {
        this.scrollY = window.scrollY
    },
    init() {
        window.addEventListener('scroll', this.onScroll.bind(this))
    }
}">
    滚动位置: <span x-text="scrollY"></span>px
</div>

初始化第三方库 #

html
<div x-data="{
    editor: null,
    init() {
        this.$nextTick(() => {
            this.editor = new Quill(this.$refs.editor, {
                theme: 'snow'
            })
        })
    }
}">
    <div x-ref="editor"></div>
</div>

x-init 指令 #

基本用法 #

html
<div x-data="{ count: 0 }" x-init="count = 10">
    <span x-text="count"></span>
</div>

调用方法 #

html
<div x-data="{
    items: [],
    loadItems() {
        this.items = ['a', 'b', 'c']
    }
}" x-init="loadItems()">
</div>

与 init 方法配合 #

x-init 先于 init 方法执行:

html
<div x-data="{
    count: 0,
    init() {
        console.log('init 方法, count:', this.count)
    }
}" x-init="count = 5; console.log('x-init, count:', count)">
</div>

输出:

text
x-init, count: 5
init 方法, count: 5

destroy 钩子 #

基本用法 #

destroy 在组件销毁时执行:

html
<div x-data="{
    timer: null,
    init() {
        this.timer = setInterval(() => {
            console.log('Tick')
        }, 1000)
    },
    destroy() {
        clearInterval(this.timer)
        console.log('组件已销毁')
    }
}">
    <p>定时器运行中</p>
</div>

清理事件监听 #

html
<div x-data="{
    scrollY: 0,
    onScroll: null,
    init() {
        this.onScroll = () => {
            this.scrollY = window.scrollY
        }
        window.addEventListener('scroll', this.onScroll)
    },
    destroy() {
        window.removeEventListener('scroll', this.onScroll)
    }
}">
    滚动位置: <span x-text="scrollY"></span>px
</div>

清理第三方库 #

html
<div x-data="{
    editor: null,
    init() {
        this.editor = new Quill(this.$refs.editor)
    },
    destroy() {
        if (this.editor) {
            this.editor = null
        }
    }
}">
    <div x-ref="editor"></div>
</div>

x-effect #

响应式副作用 #

x-effect 在依赖数据变化时执行:

html
<div x-data="{ count: 0 }" x-effect="console.log('count:', count)">
    <button @click="count++">增加</button>
</div>

初始化时执行 #

x-effect 在组件初始化时也会执行一次:

html
<div x-data="{ name: 'John' }" x-effect="document.title = name">
    <input x-model="name">
</div>

与 $watch 的区别 #

特性 x-effect $watch
自动追踪依赖 需要指定
初始执行
获取旧值

Alpine.effect #

全局副作用 #

javascript
Alpine.effect(() => {
    console.log('Store count:', Alpine.store('app').count)
})

清理副作用 #

javascript
const dispose = Alpine.effect(() => {
    console.log('effect')
})

dispose()

实用示例 #

数据获取组件 #

html
<div x-data="{
    data: null,
    loading: false,
    error: null,
    
    async init() {
        await this.fetch()
    },
    
    async fetch() {
        this.loading = true
        this.error = null
        try {
            const res = await fetch('/api/data')
            this.data = await res.json()
        } catch (e) {
            this.error = e.message
        } finally {
            this.loading = false
        }
    }
}">
    <div x-show="loading">加载中...</div>
    <div x-show="error" x-text="error"></div>
    <div x-show="data">
        <pre x-text="JSON.stringify(data, null, 2)"></pre>
    </div>
</div>

定时器组件 #

html
<div x-data="{
    time: null,
    timer: null,
    
    init() {
        this.updateTime()
        this.timer = setInterval(() => this.updateTime(), 1000)
    },
    
    destroy() {
        if (this.timer) {
            clearInterval(this.timer)
        }
    },
    
    updateTime() {
        this.time = new Date().toLocaleTimeString()
    }
}">
    当前时间: <span x-text="time"></span>
</div>

WebSocket 连接 #

html
<div x-data="{
    socket: null,
    messages: [],
    connected: false,
    
    init() {
        this.connect()
    },
    
    destroy() {
        this.disconnect()
    },
    
    connect() {
        this.socket = new WebSocket('wss://example.com/ws')
        
        this.socket.onopen = () => {
            this.connected = true
        }
        
        this.socket.onclose = () => {
            this.connected = false
        }
        
        this.socket.onmessage = (event) => {
            this.messages.push(JSON.parse(event.data))
        }
    },
    
    disconnect() {
        if (this.socket) {
            this.socket.close()
        }
    },
    
    send(message) {
        if (this.socket && this.connected) {
            this.socket.send(JSON.stringify(message))
        }
    }
}">
    <p>状态: <span x-text="connected ? '已连接' : '未连接'"></span></p>
    <template x-for="msg in messages">
        <p x-text="msg.text"></p>
    </template>
</div>

窗口尺寸监听 #

html
<div x-data="{
    width: 0,
    height: 0,
    onResize: null,
    
    init() {
        this.updateSize()
        this.onResize = () => this.updateSize()
        window.addEventListener('resize', this.onResize)
    },
    
    destroy() {
        window.removeEventListener('resize', this.onResize)
    },
    
    updateSize() {
        this.width = window.innerWidth
        this.height = window.innerHeight
    }
}">
    窗口尺寸: <span x-text="width"></span> x <span x-text="height"></span>
</div>

键盘快捷键 #

html
<div x-data="{
    shortcuts: {
        's': () => this.save(),
        'n': () => this.newItem(),
        'd': () => this.delete()
    },
    onKeyDown: null,
    
    init() {
        this.onKeyDown = (e) => {
            if (e.ctrlKey || e.metaKey) {
                const handler = this.shortcuts[e.key]
                if (handler) {
                    e.preventDefault()
                    handler()
                }
            }
        }
        window.addEventListener('keydown', this.onKeyDown)
    },
    
    destroy() {
        window.removeEventListener('keydown', this.onKeyDown)
    },
    
    save() { console.log('保存') },
    newItem() { console.log('新建') },
    delete() { console.log('删除') }
}">
    <p>快捷键: Ctrl+S 保存, Ctrl+N 新建, Ctrl+D 删除</p>
</div>

本地存储同步 #

html
<div x-data="{
    theme: 'light',
    storageKey: 'app-theme',
    
    init() {
        this.loadFromStorage()
        this.$watch('theme', () => this.saveToStorage())
    },
    
    loadFromStorage() {
        const saved = localStorage.getItem(this.storageKey)
        if (saved) {
            this.theme = saved
        }
    },
    
    saveToStorage() {
        localStorage.setItem(this.storageKey, this.theme)
    }
}">
    <select x-model="theme">
        <option value="light">浅色</option>
        <option value="dark">深色</option>
    </select>
</div>

$nextTick #

DOM 更新后执行 #

html
<div x-data="{
    items: [],
    addItem() {
        this.items.push('New Item')
        this.$nextTick(() => {
            console.log('DOM 已更新')
            this.$refs.list.lastElementChild?.scrollIntoView()
        })
    }
}">
    <ul x-ref="list">
        <template x-for="item in items">
            <li x-text="item"></li>
        </template>
    </ul>
    <button @click="addItem()">添加</button>
</div>

聚焦新元素 #

html
<div x-data="{
    showInput: false,
    focusInput() {
        this.showInput = true
        this.$nextTick(() => {
            this.$refs.input.focus()
        })
    }
}">
    <button @click="focusInput()">显示输入框</button>
    <input x-ref="input" x-show="showInput">
</div>

生命周期流程 #

text
1. x-data 创建响应式数据
2. x-init 执行(如果存在)
3. init 方法执行(如果存在)
4. x-effect 执行(如果存在)
5. 首次 DOM 渲染
6. 数据变化时 DOM 更新
7. x-if 为 false 或元素移除时 destroy 执行

最佳实践 #

1. 在 init 中初始化 #

javascript
{
    init() {
        this.loadData()
        this.setupListeners()
    }
}

2. 在 destroy 中清理 #

javascript
{
    init() {
        this.timer = setInterval(...)
    },
    destroy() {
        clearInterval(this.timer)
    }
}

3. 使用 $nextTick 操作 DOM #

javascript
{
    showElement() {
        this.visible = true
        this.$nextTick(() => {
            this.$refs.element.focus()
        })
    }
}

4. 避免在 init 中阻塞 #

javascript
{
    async init() {
        this.loading = true
        try {
            this.data = await this.fetch()
        } finally {
            this.loading = false
        }
    }
}

小结 #

生命周期要点:

  • init 在组件初始化时执行
  • destroy 在组件销毁时执行
  • x-init 指令先于 init 方法
  • x-effect 在依赖变化时执行
  • $nextTick 在 DOM 更新后执行
  • 记得清理事件监听和定时器

下一章,我们将学习高级特性。

最后更新:2026-03-28