组件通信 #

概述 #

在 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