组件通信 #
概述 #
在 Alpine.js 中,组件之间的通信是构建复杂应用的关键。本章将介绍多种组件通信方式,帮助你选择最适合的方案。
作用域继承 #
父子组件通信 #
子组件可以访问父组件的数据:
html
<div x-data="{ message: 'Hello' }">
<p x-text="message"></p>
<div x-data="{ name: 'World' }">
<p x-text="message + ' ' + name"></p>
<button @click="message = 'Hi'">修改父组件数据</button>
</div>
</div>
数据覆盖 #
子组件可以覆盖父组件的同名数据:
html
<div x-data="{ name: 'Parent' }">
<p x-text="name"></p>
<div x-data="{ name: 'Child' }">
<p x-text="name"></p>
</div>
</div>
事件通信 #
$dispatch 派发事件 #
子组件向父组件发送事件:
html
<div x-data="{ count: 0 }" @increment="count++">
<p>计数: <span x-text="count"></span></p>
<div x-data>
<button @click="$dispatch('increment')">增加</button>
</div>
</div>
传递数据 #
html
<div x-data="{ user: null }" @set-user="user = $event.detail">
<p x-text="user?.name"></p>
<div x-data>
<button
@click="$dispatch('set-user', { name: 'John', email: 'john@example.com' })"
>
设置用户
</button>
</div>
</div>
自定义事件对象 #
html
<div x-data="{
messages: [],
addMessage(event) {
this.messages.push({
id: Date.now(),
text: event.detail
})
}
}" @message="addMessage($event)">
<template x-for="msg in messages">
<p x-text="msg.text"></p>
</template>
<div x-data>
<input x-ref="input" placeholder="输入消息">
<button @click="$dispatch('message', $refs.input.value)">发送</button>
</div>
</div>
全局状态 #
Alpine.store #
使用全局 Store 实现跨组件通信:
javascript
Alpine.store('cart', {
items: [],
add(item) {
this.items.push(item)
},
remove(id) {
this.items = this.items.filter(i => i.id !== id)
},
get total() {
return this.items.reduce((sum, i) => sum + i.price, 0)
}
})
html
<div x-data>
<h3>商品列表</h3>
<button @click="$store.cart.add({ id: 1, name: 'Product', price: 100 })">
添加到购物车
</button>
</div>
<div x-data>
<h3>购物车</h3>
<template x-for="item in $store.cart.items">
<p x-text="item.name"></p>
</template>
<p>总计: <span x-text="$store.cart.total"></span></p>
</div>
Alpine.reactive #
使用响应式对象:
javascript
const sharedState = Alpine.reactive({
user: null,
setUser(user) {
this.user = user
}
})
html
<div x-data>
<p x-text="sharedState.user?.name"></p>
<button @click="sharedState.setUser({ name: 'John' })">设置用户</button>
</div>
<div x-data>
<p x-text="sharedState.user?.name"></p>
</div>
<script>
const sharedState = Alpine.reactive({
user: null,
setUser(user) { this.user = user }
})
</script>
Props 模式 #
通过属性传递数据 #
html
<div x-data="{ items: ['a', 'b', 'c'] }">
<div x-data="listComponent(items)">
<template x-for="item in list">
<p x-text="item"></p>
</template>
</div>
</div>
<script>
function listComponent(items) {
return {
list: items
}
}
</script>
配置对象 #
html
<div x-data="dataTable({
apiUrl: '/api/users',
columns: ['name', 'email'],
perPage: 10
})">
</div>
<script>
Alpine.data('dataTable', (config) => ({
data: [],
loading: false,
apiUrl: config.apiUrl,
columns: config.columns,
perPage: config.perPage,
async fetchData() {
this.loading = true
const res = await fetch(this.apiUrl)
this.data = await res.json()
this.loading = false
},
init() {
this.fetchData()
}
}))
</script>
事件总线 #
创建事件总线 #
javascript
Alpine.magic('bus', () => {
const listeners = {}
return {
on(event, callback) {
if (!listeners[event]) listeners[event] = []
listeners[event].push(callback)
},
off(event, callback) {
if (!listeners[event]) return
listeners[event] = listeners[event].filter(cb => cb !== callback)
},
emit(event, data) {
if (!listeners[event]) return
listeners[event].forEach(cb => cb(data))
}
}
})
使用事件总线 #
html
<div x-data x-init="
$bus.on('notification', (msg) => {
alert('收到通知: ' + msg)
})
">
<p>监听通知事件</p>
</div>
<div x-data>
<button @click="$bus.emit('notification', 'Hello!')">
发送通知
</button>
</div>
实用示例 #
父子表单 #
html
<div x-data="{
user: { name: '', email: '' },
submitted: false,
handleSubmit() {
this.submitted = true
console.log('提交:', this.user)
}
}">
<form @submit.prevent="handleSubmit()">
<div x-data="inputField('name', user.name)" x-init="watchValue('name')">
<label>姓名</label>
<input x-model="value" @input="updateParent()">
</div>
<div x-data="inputField('email', user.email)" x-init="watchValue('email')">
<label>邮箱</label>
<input x-model="value" @input="updateParent()">
</div>
<button type="submit">提交</button>
</form>
<p x-show="submitted">已提交!</p>
</div>
<script>
function inputField(field, initialValue) {
return {
value: initialValue,
field: field,
parent: null,
init() {
this.parent = this.$el.closest('[x-data]')
},
updateParent() {
const parentData = Alpine.$data(this.parent)
parentData.user[this.field] = this.value
},
watchValue(fieldName) {
this.$watch('value', () => this.updateParent())
}
}
}
</script>
购物车系统 #
javascript
Alpine.store('cart', {
items: [],
add(product) {
const existing = this.items.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
this.items.push({ ...product, quantity: 1 })
}
this.notify('added', product)
},
remove(productId) {
const item = this.items.find(i => i.id === productId)
this.items = this.items.filter(i => i.id !== productId)
this.notify('removed', item)
},
notify(event, data) {
window.dispatchEvent(new CustomEvent(`cart:${event}`, { detail: data }))
}
})
html
<div x-data x-init="
window.addEventListener('cart:added', (e) => {
console.log('添加商品:', e.detail)
})
">
<h3>商品列表</h3>
<button @click="$store.cart.add({ id: 1, name: 'Product A', price: 100 })">
添加商品 A
</button>
</div>
<div x-data>
<h3>购物车 (<span x-text="$store.cart.items.length"></span>)</h3>
<template x-for="item in $store.cart.items">
<div>
<span x-text="item.name"></span>
<span x-text="item.quantity"></span>
<button @click="$store.cart.remove(item.id)">删除</button>
</div>
</template>
</div>
标签页同步 #
html
<div x-data="{ activeTab: 'home' }">
<div x-data="tabNav()" @tab-change="activeTab = $event.detail">
<button @click="changeTab('home')">首页</button>
<button @click="changeTab('about')">关于</button>
<button @click="changeTab('contact')">联系</button>
</div>
<div x-data="tabContent(activeTab)" @tab-change="currentTab = $event.detail">
<div x-show="currentTab === 'home'">首页内容</div>
<div x-show="currentTab === 'about'">关于内容</div>
<div x-show="currentTab === 'contact'">联系内容</div>
</div>
</div>
<script>
Alpine.data('tabNav', () => ({
changeTab(tab) {
this.$dispatch('tab-change', tab)
}
}))
Alpine.data('tabContent', (initial) => ({
currentTab: initial
}))
</script>
通信方式对比 #
| 方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 作用域继承 | 简单父子关系 | 简单直接 | 耦合度高 |
| $dispatch | 子向父通信 | 解耦 | 单向传递 |
| Alpine.store | 全局状态 | 跨组件共享 | 状态管理复杂 |
| Props | 父向子传递 | 明确的数据流 | 需要手动传递 |
| 事件总线 | 任意组件 | 完全解耦 | 调试困难 |
最佳实践 #
1. 选择合适的通信方式 #
- 简单父子关系:作用域继承
- 子向父通信:$dispatch
- 跨组件通信:Alpine.store
- 复杂应用:组合使用
2. 避免过度使用全局状态 #
javascript
Alpine.store('app', {
user: null,
theme: 'light'
})
Alpine.store('cart', {
items: []
})
3. 清晰的事件命名 #
html
@user:login
@user:logout
@cart:add
@cart:remove
@notification:show
4. 文档化通信接口 #
javascript
Alpine.data('searchBox', () => ({
// 派发事件: search(query)
// 监听事件: clear
}))
小结 #
组件通信要点:
- 作用域继承实现父子通信
$dispatch派发自定义事件Alpine.store管理全局状态- Props 模式传递配置
- 事件总线实现完全解耦
下一章,我们将学习组件生命周期。
最后更新:2026-03-28