Alpine.reactive 响应式对象 #

什么是 Alpine.reactive? #

Alpine.reactive 用于创建独立的响应式对象。与 x-dataAlpine.store 不同,它可以在任何 JavaScript 代码中使用,提供更灵活的状态管理方式。

基本语法 #

javascript
const state = Alpine.reactive({
    property: 'value'
})

基本用法 #

创建响应式对象 #

javascript
const counter = Alpine.reactive({
    count: 0,
    increment() {
        this.count++
    },
    decrement() {
        this.count--
    }
})

在组件中使用 #

html
<div x-data>
    <span x-text="counter.count"></span>
    <button @click="counter.increment()">增加</button>
</div>

<script>
const counter = Alpine.reactive({
    count: 0,
    increment() { this.count++ }
})
</script>

与 Alpine.store 的区别 #

特性 Alpine.reactive Alpine.store
命名 无需命名 需要名称
访问方式 直接引用 $store.name
注册时机 任意时刻 alpine:init 事件
使用场景 局部状态、模块状态 全局状态

响应式特性 #

自动追踪依赖 #

javascript
const state = Alpine.reactive({
    firstName: 'John',
    lastName: 'Doe'
})

Alpine.effect(() => {
    console.log('Full name:', state.firstName, state.lastName)
})

state.firstName = 'Jane'

嵌套对象响应式 #

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

state.user.profile.name = 'Jane'

数组响应式 #

javascript
const state = Alpine.reactive({
    items: ['a', 'b', 'c']
})

state.items.push('d')
state.items.pop()

实用示例 #

表单状态管理 #

javascript
const formState = Alpine.reactive({
    data: {
        name: '',
        email: '',
        message: ''
    },
    errors: {},
    loading: false,
    
    validate() {
        this.errors = {}
        if (!this.data.name) this.errors.name = '请输入姓名'
        if (!this.data.email) this.errors.email = '请输入邮箱'
        return Object.keys(this.errors).length === 0
    },
    
    async submit() {
        if (!this.validate()) return
        
        this.loading = true
        try {
            await fetch('/api/contact', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(this.data)
            })
            this.reset()
        } finally {
            this.loading = false
        }
    },
    
    reset() {
        this.data = { name: '', email: '', message: '' }
        this.errors = {}
    }
})
html
<form x-data @submit.prevent="formState.submit()">
    <div>
        <input x-model="formState.data.name" placeholder="姓名">
        <span x-show="formState.errors.name" x-text="formState.errors.name"></span>
    </div>
    <div>
        <input x-model="formState.data.email" placeholder="邮箱">
        <span x-show="formState.errors.email" x-text="formState.errors.email"></span>
    </div>
    <div>
        <textarea x-model="formState.data.message" placeholder="消息"></textarea>
    </div>
    <button type="submit" :disabled="formState.loading">
        <span x-show="!formState.loading">提交</span>
        <span x-show="formState.loading">提交中...</span>
    </button>
</form>

数据表格状态 #

javascript
const tableState = Alpine.reactive({
    data: [],
    loading: false,
    page: 1,
    perPage: 10,
    sortField: 'id',
    sortOrder: 'asc',
    filters: {},
    
    get total() {
        return this.data.length
    },
    
    get totalPages() {
        return Math.ceil(this.total / this.perPage)
    },
    
    get paginatedData() {
        let result = [...this.data]
        
        for (const [field, value] of Object.entries(this.filters)) {
            if (value) {
                result = result.filter(item => 
                    String(item[field]).toLowerCase().includes(value.toLowerCase())
                )
            }
        }
        
        result.sort((a, b) => {
            const aVal = a[this.sortField]
            const bVal = b[this.sortField]
            const order = this.sortOrder === 'asc' ? 1 : -1
            return aVal > bVal ? order : -order
        })
        
        const start = (this.page - 1) * this.perPage
        return result.slice(start, start + this.perPage)
    },
    
    async fetchData() {
        this.loading = true
        try {
            const res = await fetch('/api/data')
            this.data = await res.json()
        } finally {
            this.loading = false
        }
    },
    
    sort(field) {
        if (this.sortField === field) {
            this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'
        } else {
            this.sortField = field
            this.sortOrder = 'asc'
        }
    },
    
    goToPage(page) {
        this.page = Math.max(1, Math.min(page, this.totalPages))
    }
})

搜索状态 #

javascript
const searchState = Alpine.reactive({
    query: '',
    results: [],
    loading: false,
    selectedIndex: -1,
    
    async search() {
        if (this.query.length < 2) {
            this.results = []
            return
        }
        
        this.loading = true
        try {
            const res = await fetch(`/api/search?q=${encodeURIComponent(this.query)}`)
            this.results = await res.json()
            this.selectedIndex = -1
        } finally {
            this.loading = false
        }
    },
    
    selectNext() {
        if (this.selectedIndex < this.results.length - 1) {
            this.selectedIndex++
        }
    },
    
    selectPrevious() {
        if (this.selectedIndex > 0) {
            this.selectedIndex--
        }
    },
    
    selectCurrent() {
        if (this.selectedIndex >= 0) {
            const result = this.results[this.selectedIndex]
            console.log('Selected:', result)
            this.results = []
        }
    }
})

状态机 #

javascript
const createStateMachine = (states, initial) => {
    return Alpine.reactive({
        current: initial,
        states,
        
        can(transition) {
            return this.states[this.current]?.transitions?.includes(transition)
        },
        
        transition(name) {
            if (this.can(name)) {
                this.current = name
                return true
            }
            return false
        },
        
        is(state) {
            return this.current === state
        }
    })
}

const orderState = createStateMachine({
    pending: { transitions: ['confirmed', 'cancelled'] },
    confirmed: { transitions: ['shipped', 'cancelled'] },
    shipped: { transitions: ['delivered'] },
    delivered: { transitions: [] },
    cancelled: { transitions: [] }
}, 'pending')

配合 Alpine.effect #

自动响应变化 #

javascript
const state = Alpine.reactive({
    count: 0
})

Alpine.effect(() => {
    console.log('Count changed:', state.count)
    document.title = `Count: ${state.count}`
})

state.count++

清理副作用 #

javascript
const state = Alpine.reactive({ count: 0 })

const dispose = Alpine.effect(() => {
    console.log(state.count)
})

dispose()

嵌套 effect #

javascript
const state = Alpine.reactive({
    user: { name: 'John' },
    settings: { theme: 'light' }
})

Alpine.effect(() => {
    console.log('User:', state.user.name)
    
    Alpine.effect(() => {
        console.log('Theme:', state.settings.theme)
    })
})

与组件集成 #

通过 x-data 引用 #

html
<div x-data="{
    get state() { return externalState }
}">
    <span x-text="state.count"></span>
</div>

<script>
const externalState = Alpine.reactive({
    count: 0
})
</script>

通过 Alpine.data #

javascript
const sharedState = Alpine.reactive({
    items: []
})

Alpine.data('listComponent', () => ({
    get items() {
        return sharedState.items
    },
    
    addItem(item) {
        sharedState.items.push(item)
    }
}))

最佳实践 #

1. 模块化状态 #

javascript
const createUserState = () => Alpine.reactive({
    user: null,
    loading: false,
    
    async fetch(id) {
        this.loading = true
        this.user = await fetch(`/api/users/${id}`).then(r => r.json())
        this.loading = false
    }
})

const userState = createUserState()

2. 封装操作 #

javascript
const state = Alpine.reactive({
    _items: [],
    
    get items() {
        return this._items
    },
    
    addItem(item) {
        this._items = [...this._items, item]
    },
    
    removeItem(id) {
        this._items = this._items.filter(item => item.id !== id)
    }
})

3. 类型安全 #

javascript
const createCounter = (initial = 0) => Alpine.reactive({
    count: initial,
    min: 0,
    max: 100,
    
    increment() {
        if (this.count < this.max) this.count++
    },
    
    decrement() {
        if (this.count > this.min) this.count--
    },
    
    reset() {
        this.count = initial
    }
})

注意事项 #

1. 解构失去响应性 #

javascript
const state = Alpine.reactive({ count: 0 })
const { count } = state
count++

解决:

javascript
const state = Alpine.reactive({ count: 0 })
state.count++

2. 替换整个对象 #

javascript
const state = Alpine.reactive({ count: 0 })
state = { count: 1 }

解决:

javascript
const state = Alpine.reactive({ count: 0 })
Object.assign(state, { count: 1 })

3. this 绑定 #

javascript
const state = Alpine.reactive({
    count: 0,
    increment() {
        this.count++
    }
})

const fn = state.increment
fn()

解决:

javascript
const fn = state.increment.bind(state)

小结 #

Alpine.reactive 要点:

  • 创建独立的响应式对象
  • 可在任何 JavaScript 代码中使用
  • 配合 Alpine.effect 实现响应式副作用
  • 适合模块化状态管理
  • 注意解构和替换对象的问题

下一章,我们将学习组件化开发。

最后更新:2026-03-28