响应式基础 #

什么是响应式? #

响应式是一种编程范式,当数据发生变化时,依赖这些数据的视图会自动更新。Alpine.js 的响应式系统让开发者无需手动操作 DOM,只需关注数据的变化。

html
<div x-data="{ count: 0 }">
    <button @click="count++">增加</button>
    <span x-text="count"></span>
</div>

count 变化时,x-text 绑定的内容会自动更新。

响应式原理 #

Proxy 代理 #

Alpine.js 使用 JavaScript 的 Proxy 对象实现响应式:

javascript
const data = { count: 0 }

const reactive = new Proxy(data, {
    get(target, key) {
        track(target, key)
        return target[key]
    },
    set(target, key, value) {
        target[key] = value
        trigger(target, key)
        return true
    }
})

依赖收集 #

当组件渲染时,Alpine.js 会记录哪些数据被使用:

html
<div x-data="{ count: 0, name: 'John' }">
    <span x-text="count"></span>
</div>

只有 count 被使用,name 变化不会触发更新。

触发更新 #

当数据变化时,Alpine.js 会通知所有依赖该数据的组件重新渲染:

html
<div x-data="{ count: 0 }">
    <span x-text="count"></span>
    <span x-text="count * 2"></span>
    <button @click="count++">增加</button>
</div>

点击按钮后,两个 span 都会更新。

响应式数据 #

在 x-data 中定义 #

html
<div x-data="{
    message: 'Hello',
    count: 0,
    user: { name: 'John' },
    items: ['a', 'b', 'c']
}">
</div>

使用 Alpine.reactive #

创建独立的响应式对象:

javascript
const state = Alpine.reactive({
    count: 0,
    user: { name: 'John' }
})

数据变更检测 #

能检测的变更 #

javascript
this.count = 10
this.user.name = 'Jane'
this.items.push('d')
this.items.pop()
this.items.splice(0, 1)

不能检测的变更 #

javascript
this.items[0] = 'new'
this.items.length = 0
Object.assign(this.user, { email: 'test@example.com' })

解决方案 #

javascript
this.items = [...this.items]
this.items[0] = 'new'
this.items = this.items.filter(item => item.active)
this.user = { ...this.user, email: 'test@example.com' }

$watch 监听器 #

基本用法 #

监听数据变化:

html
<div x-data="{
    count: 0
}" x-init="
    $watch('count', (value, oldValue) => {
        console.log(`count 从 ${oldValue} 变为 ${value}`)
    })
">
    <button @click="count++">增加</button>
</div>

监听嵌套属性 #

html
<div x-data="{
    user: { name: 'John' }
}" x-init="
    $watch('user.name', (value) => {
        console.log('name changed:', value)
    })
">
    <input x-model="user.name">
</div>

监听数组 #

html
<div x-data="{
    items: ['a', 'b', 'c']
}" x-init="
    $watch('items', (value) => {
        console.log('items changed:', value)
    })
">
    <button @click="items.push('d')">添加</button>
</div>

深度监听 #

html
<div x-data="{
    user: {
        profile: {
            name: 'John'
        }
    }
}" x-init="
    $watch('user', (value) => {
        console.log('user changed:', value)
    }, { deep: true })
">
    <input x-model="user.profile.name">
</div>

x-effect #

基本用法 #

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

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

与 $watch 的区别 #

特性 x-effect $watch
自动追踪依赖 需要指定属性
返回旧值
使用场景 副作用 监听特定变化

实用示例 #

html
<div x-data="{
    firstName: 'John',
    lastName: 'Doe',
    fullName: ''
}" x-effect="fullName = firstName + ' ' + lastName">
    <input x-model="firstName">
    <input x-model="lastName">
    <p x-text="fullName"></p>
</div>

Alpine.effect #

全局副作用 #

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

清理副作用 #

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

cleanup()

计算属性 #

使用 getter #

html
<div x-data="{
    firstName: 'John',
    lastName: 'Doe',
    get fullName() {
        return this.firstName + ' ' + this.lastName
    }
}">
    <p x-text="fullName"></p>
</div>

计算属性缓存 #

计算属性会缓存结果,只有依赖变化时才重新计算:

html
<div x-data="{
    items: [1, 2, 3, 4, 5],
    get sum() {
        console.log('计算 sum')
        return this.items.reduce((a, b) => a + b, 0)
    }
}">
    <p x-text="sum"></p>
    <p x-text="sum"></p>
    <button @click="items.push(6)">添加</button>
</div>

响应式最佳实践 #

1. 避免深层嵌套 #

javascript
{
    user: {
        profile: {
            address: {
                city: 'Beijing'
            }
        }
    }
}

推荐扁平化:

javascript
{
    userCity: 'Beijing'
}

2. 使用不可变操作 #

javascript
this.items = [...this.items, newItem]
this.items = this.items.filter(item => item.id !== id)

3. 合理使用计算属性 #

html
<div x-data="{
    items: [],
    get activeItems() {
        return this.items.filter(item => item.active)
    },
    get itemCount() {
        return this.items.length
    }
}">
</div>

4. 避免在模板中复杂计算 #

不推荐:

html
<span x-text="items.filter(i => i.active).map(i => i.name).join(', ')"></span>

推荐:

html
<span x-text="activeItemNames"></span>
javascript
get activeItemNames() {
    return this.items
        .filter(i => i.active)
        .map(i => i.name)
        .join(', ')
}

常见问题 #

1. 数据更新但视图不更新 #

javascript
this.items[0] = 'new'

解决:

javascript
this.items = [...this.items]

2. 新属性不响应 #

javascript
this.newProp = 'value'

解决:

javascript
this.data = { ...this.data, newProp: 'value' }

3. 监听不触发 #

确保使用正确的方式修改数据:

javascript
this.items.push('new')
this.items = this.items

小结 #

响应式系统要点:

  • 使用 Proxy 实现响应式
  • 自动收集依赖和触发更新
  • $watch 监听数据变化
  • x-effect 执行响应式副作用
  • 使用 getter 定义计算属性
  • 注意数据变更检测的限制

下一章,我们将学习 Alpine.store 全局状态管理。

最后更新:2026-03-28