數據綁定與狀態管理
uni-app基於Vue.js開發,完全支援Vue.js的數據綁定和狀態管理特性。本文將詳細介紹在uni-app中如何進行數據綁定以及使用不同的狀態管理方案。
數據綁定基礎
數據聲明
在uni-app中,頁面的數據需要在data選項中聲明:
javascript
export default {
data() {
return {
message: 'Hello uni-app',
count: 0,
isActive: false,
user: {
name: '張三',
age: 25
},
list: ['蘋果', '香蕉', '橙子']
}
}
}文本插值
使用雙大括號語法(Mustache語法)進行文本插值:
html
<view>{{ message }}</view>
<view>計數:{{ count }}</view>
<view>用戶名:{{ user.name }},年齡:{{ user.age }}</view>屬性綁定
使用v-bind指令(簡寫為:)綁定HTML屬性:
html
<view :class="isActive ? 'active' : ''">動態類名</view>
<image :src="imageUrl" mode="aspectFit"></image>
<view :style="{ color: textColor, fontSize: fontSize + 'px' }">動態樣式</view>雙向綁定
使用v-model指令實現表單元素的雙向數據綁定:
html
<input v-model="message" placeholder="請輸入內容" />
<textarea v-model="content" placeholder="請輸入詳細描述"></textarea>
<switch v-model="isChecked"></switch>
<slider v-model="sliderValue" min="0" max="100"></slider>列表渲染
使用v-for指令渲染列表:
html
<view v-for="(item, index) in list" :key="index">
{{ index + 1 }}. {{ item }}
</view>
<view v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</view>事件綁定
使用v-on指令(簡寫為@)綁定事件:
html
<button @click="increment">計數器 +1</button>
<view @tap="handleTap">點擊我</view>
<input @input="handleInput" />事件處理方法定義在methods選項中:
javascript
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
},
handleTap(event) {
console.log('視圖被點擊', event)
},
handleInput(event) {
console.log('輸入內容:', event.detail.value)
}
}
}計算屬性與偵聽器
計算屬性
使用computed選項定義計算屬性:
javascript
export default {
data() {
return {
price: 100,
quantity: 2
}
},
computed: {
// 計算總價
totalPrice() {
return this.price * this.quantity
},
// 帶有getter和setter的計算屬性
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[1]
}
}
}
}在模板中使用計算屬性:
html
<view>總價:{{ totalPrice }}元</view>
<view>全名:{{ fullName }}</view>偵聽器
使用watch選項監聽數據變化:
javascript
export default {
data() {
return {
question: '',
answer: ''
}
},
watch: {
// 監聽question變化
question(newVal, oldVal) {
if (newVal.trim().endsWith('?')) {
this.getAnswer()
}
},
// 深度監聽對象變化
userInfo: {
handler(newVal, oldVal) {
console.log('用戶信息變化了', newVal)
},
deep: true
},
// 立即執行的偵聽器
searchText: {
handler(newVal) {
this.fetchSearchResults(newVal)
},
immediate: true
}
},
methods: {
getAnswer() {
this.answer = '思考中...'
// 模擬API請求
setTimeout(() => {
this.answer = '這是一個回答'
}, 1000)
},
fetchSearchResults(text) {
// 搜索邏輯
}
}
}組件通信
父子組件通信
- 父組件向子組件傳遞數據(Props)
子組件定義props:
javascript
// 子組件 child.vue
export default {
props: {
// 基礎類型檢查
title: String,
// 多種類型
id: [String, Number],
// 必填項
content: {
type: String,
required: true
},
// 帶有默認值
showFooter: {
type: Boolean,
default: false
},
// 對象/數組的默認值
config: {
type: Object,
default() {
return { theme: 'default' }
}
},
// 自定義驗證函數
priority: {
validator(value) {
return ['high', 'medium', 'low'].includes(value)
}
}
}
}父組件傳遞props:
html
<!-- 父組件 -->
<child-component
title="標題"
:id="itemId"
content="這是內容"
:show-footer="true"
:config="{ theme: 'dark' }"
priority="high"
></child-component>- 子組件向父組件傳遞事件(Events)
子組件觸發事件:
javascript
// 子組件
export default {
methods: {
submit() {
// 觸發自定義事件,並傳遞數據
this.$emit('submit', { id: 1, data: 'some data' })
}
}
}父組件監聽事件:
html
<!-- 父組件 -->
<child-component @submit="handleSubmit"></child-component>javascript
// 父組件
export default {
methods: {
handleSubmit(data) {
console.log('收到子組件數據', data)
}
}
}跨組件通信
- 使用事件總線(EventBus)
創建事件總線:
javascript
// eventBus.js
import Vue from 'vue'
export const eventBus = new Vue()
// 在Vue 3中可以使用mitt庫
// import mitt from 'mitt'
// export const eventBus = mitt()組件A發送事件:
javascript
// 組件A
import { eventBus } from '@/utils/eventBus'
export default {
methods: {
sendMessage() {
eventBus.$emit('message', '這是來自組件A的消息')
}
}
}組件B接收事件:
javascript
// 組件B
import { eventBus } from '@/utils/eventBus'
export default {
data() {
return {
message: ''
}
},
created() {
// 監聽事件
eventBus.$on('message', this.receiveMessage)
},
beforeDestroy() {
// 移除監聽
eventBus.$off('message', this.receiveMessage)
},
methods: {
receiveMessage(msg) {
this.message = msg
}
}
}- 使用provide/inject
祖先組件提供數據:
javascript
// 祖先組件
export default {
provide() {
return {
theme: this.theme,
// 提供方法
updateTheme: this.updateTheme
}
},
data() {
return {
theme: 'light'
}
},
methods: {
updateTheme(newTheme) {
this.theme = newTheme
}
}
}後代組件注入數據:
javascript
// 後代組件
export default {
inject: ['theme', 'updateTheme'],
methods: {
changeTheme() {
this.updateTheme('dark')
}
}
}狀態管理
全局變量
對於簡單應用,可以使用全局變量進行狀態管理:
javascript
// App.vue
export default {
globalData: {
userInfo: null,
token: '',
settings: {}
},
onLaunch() {
// 初始化全局數據
},
methods: {
// 全局方法
updateUserInfo(userInfo) {
this.globalData.userInfo = userInfo
}
}
}在頁面或組件中使用:
javascript
// 頁面或組件
export default {
methods: {
getUserInfo() {
const app = getApp()
return app.globalData.userInfo
},
login() {
// 登錄成功後更新全局數據
getApp().updateUserInfo({ name: '張三', id: '123' })
}
}
}Vuex狀態管理
對於複雜應用,推薦使用Vuex進行狀態管理:
- 安裝Vuex
bash
npm install vuex --save- 創建Store
javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
hasLogin: false,
userInfo: {},
cartItems: []
},
getters: {
cartCount(state) {
return state.cartItems.length
},
isVip(state) {
return state.userInfo.vip === true
}
},
mutations: {
login(state, userInfo) {
state.hasLogin = true
state.userInfo = userInfo
},
logout(state) {
state.hasLogin = false
state.userInfo = {}
},
addToCart(state, item) {
state.cartItems.push(item)
}
},
actions: {
// 異步操作
loginAction({ commit }, username) {
return new Promise((resolve, reject) => {
// 模擬API請求
setTimeout(() => {
const userInfo = { name: username, id: Date.now() }
commit('login', userInfo)
resolve(userInfo)
}, 1000)
})
},
// 帶有條件判斷的action
checkoutAction({ state, commit }, payload) {
if (state.cartItems.length === 0) {
return Promise.reject('購物車為空')
}
// 結賬邏輯
return api.checkout(state.cartItems)
.then(() => {
commit('clearCart')
return '結賬成功'
})
}
},
modules: {
// 模塊化狀態管理
products: {
namespaced: true,
state: { list: [] },
mutations: { /* ... */ },
actions: { /* ... */ }
},
orders: {
namespaced: true,
state: { list: [] },
mutations: { /* ... */ },
actions: { /* ... */ }
}
}
})- 在main.js中掛載Store
javascript
// main.js
import Vue from 'vue'
import App from './App'
import store from './store'
Vue.prototype.$store = store
const app = new Vue({
store,
...App
})
app.$mount()- 在組件中使用Vuex
javascript
// 組件中
export default {
computed: {
// 映射state
...Vuex.mapState(['hasLogin', 'userInfo']),
// 映射getters
...Vuex.mapGetters(['cartCount', 'isVip']),
// 自定義計算屬性
welcomeMessage() {
return this.hasLogin ? `歡迎回來,${this.userInfo.name}` : '請登錄'
}
},
methods: {
// 映射mutations
...Vuex.mapMutations(['logout', 'addToCart']),
// 映射actions
...Vuex.mapActions(['loginAction', 'checkoutAction']),
// 使用示例
async handleLogin() {
try {
const userInfo = await this.loginAction('張三')
uni.showToast({ title: '登錄成功' })
} catch (error) {
uni.showToast({ title: '登錄失敗', icon: 'none' })
}
},
// 使用命名空間的模塊
loadProducts() {
this.$store.dispatch('products/loadList')
}
}
}Pinia狀態管理(Vue 3)
如果您使用的是Vue 3版本的uni-app,可以使用更現代的Pinia進行狀態管理:
- 安裝Pinia
bash
npm install pinia --save- 創建Store
javascript
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
hasLogin: false,
userInfo: {}
}),
getters: {
isVip: (state) => state.userInfo.vip === true,
fullName: (state) => {
return state.userInfo.firstName + ' ' + state.userInfo.lastName
}
},
actions: {
async login(username, password) {
// 模擬API請求
const userInfo = await api.login(username, password)
this.hasLogin = true
this.userInfo = userInfo
return userInfo
},
logout() {
this.hasLogin = false
this.userInfo = {}
}
}
})
// stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),
getters: {
count: (state) => state.items.length,
totalPrice: (state) => {
return state.items.reduce((total, item) => {
return total + item.price * item.quantity
}, 0)
}
},
actions: {
addItem(item) {
this.items.push(item)
},
removeItem(id) {
const index = this.items.findIndex(item => item.id === id)
if (index !== -1) {
this.items.splice(index, 1)
}
},
async checkout() {
if (this.items.length === 0) {
throw new Error('購物車為空')
}
await api.checkout(this.items)
this.items = []
}
}
})- 在main.js中掛載Pinia
javascript
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
app.use(pinia)
return {
app
}
}- 在組件中使用Pinia
vue
<template>
<view>
<view v-if="userStore.hasLogin">
歡迎回來,{{ userStore.userInfo.name }}
<button @click="logout">退出登錄</button>
</view>
<view v-else>
<button @click="login">登錄</button>
</view>
<view>購物車: {{ cartStore.count }}件商品</view>
<view>總價: {{ cartStore.totalPrice }}元</view>
<button @click="addRandomItem">添加商品</button>
<button @click="checkout">結賬</button>
</view>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'
const userStore = useUserStore()
const cartStore = useCartStore()
function login() {
userStore.login('張三', '123456')
.then(() => {
uni.showToast({ title: '登錄成功' })
})
.catch(() => {
uni.showToast({ title: '登錄失敗', icon: 'none' })
})
}
function logout() {
userStore.logout()
uni.showToast({ title: '已退出登錄' })
}
function addRandomItem() {
const id = Math.floor(Math.random() * 1000)
cartStore.addItem({
id,
name: `商品${id}`,
price: Math.floor(Math.random() * 100) + 1,
quantity: 1
})
}
function checkout() {
cartStore.checkout()
.then(() => {
uni.showToast({ title: '結賬成功' })
})
.catch((error) => {
uni.showToast({ title: error.message, icon: 'none' })
})
}
</script>持久化狀態
為了在應用重啟後保持狀態,可以將狀態持久化到本地存儲:
手動持久化
javascript
// 保存狀態
uni.setStorageSync('userInfo', JSON.stringify(this.userInfo))
// 恢復狀態
try {
const userInfo = JSON.parse(uni.getStorageSync('userInfo') || '{}')
this.userInfo = userInfo
} catch (e) {
console.error('解析用戶信息失敗', e)
}Vuex持久化
使用插件實現Vuex狀態自動持久化:
javascript
// store/plugins/persistedState.js
import createPersistedState from 'vuex-persistedstate'
const persistedState = createPersistedState({
storage: {
getItem: key => uni.getStorageSync(key),
setItem: (key, value) => uni.setStorageSync(key, value),
removeItem: key => uni.removeStorageSync(key)
},
// 只持久化部分狀態
paths: ['hasLogin', 'userInfo', 'token']
})
export default persistedState在Store中使用插件:
javascript
// store/index.js
import persistedState from './plugins/persistedState'
export default new Vuex.Store({
// ...state, mutations, actions
plugins: [persistedState]
})Pinia持久化
使用pinia-plugin-persistedstate插件:
javascript
// main.js
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
return {
app
}
}在Store中配置持久化:
javascript
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
hasLogin: false,
userInfo: {}
}),
// 持久化配置
persist: {
enabled: true,
strategies: [
{
key: 'user-store',
storage: {
getItem: (key) => uni.getStorageSync(key),
setItem: (key, value) => uni.setStorageSync(key, value),
removeItem: (key) => uni.removeStorageSync(key)
},
paths: ['hasLogin', 'userInfo'] // 只持久化部分狀態
}
]
},
// getters, actions...
})最佳實踐
1. 合理劃分狀態
- 組件內狀態:僅在組件內使用的狀態,放在組件的
data中 - 共享狀態:多個組件共享的狀態,放在Vuex/Pinia中
- 持久化狀態:需要在應用重啟後保持的狀態,配置持久化
2. 避免過度使用計算屬性
計算屬性會緩存結果,但過度使用會增加內存佔用:
javascript
// 不推薦
computed: {
item1() { return this.list[0] },
item2() { return this.list[1] },
item3() { return this.list[2] }
}
// 推薦
computed: {
firstThreeItems() {
return this.list.slice(0, 3)
}
}3. 使用函數式更新
對於複雜狀態,使用函數式更新可以避免意外修改:
javascript
// 不推薦
this.userInfo.age += 1
// 推薦
this.userInfo = {
...this.userInfo,
age: this.userInfo.age + 1
}
// Vuex中
mutations: {
updateUserAge(state, age) {
state.userInfo = {
...state.userInfo,
age
}
}
}4. 模塊化狀態管理
對於大型應用,將狀態分模塊管理:
javascript
// Vuex模塊化
modules: {
user: userModule,
product: productModule,
order: orderModule,
cart: cartModule
}
// Pinia天然支援模塊化
const useUserStore = defineStore('user', { /* ... */ })
const useProductStore = defineStore('product', { /* ... */ })
const useOrderStore = defineStore('order', { /* ... */ })
const useCartStore = defineStore('cart', { /* ... */ })5. 性能優化
- 避免深層嵌套:狀態結構盡量扁平化
- 使用不可變數據:修改數據時創建新對象而不是直接修改
- 合理使用getters:將複雜計算邏輯放在getters中
- 按需加載模塊:使用動態導入減少初始加載時間
總結
uni-app提供了完整的數據綁定和狀態管理能力,從簡單的組件內狀態到複雜的全局狀態管理都有對應的解決方案。在實際開發中,應根據應用的複雜度選擇合適的狀態管理方式,並遵循最佳實踐,以確保應用的可維護性和性能。