uniapp開發鴻蒙:數據綁定與狀態管理實戰
引入:響應式數據的重要性
在uniapp開發中,數據綁定與狀態管理是構建複雜應用的核心。Vue3的組合式API為我們提供了更靈活、更強大的響應式系統,而Pinia作為新一代狀態管理工具,讓跨組件、跨頁面的數據共享變得更加簡單高效。今天,我們將深入探討uniapp在鴻蒙平台下的數據綁定與狀態管理方案。
一、Vue3組合式API:ref與reactive
1.1 ref:基本類型的響應式包裝
ref是組合式API中最常用的響應式工具,用於包裝基本類型數據:
import { ref } from 'vue'
// 創建響應式數據
const count = ref(0)
const message = ref('Hello')
const isActive = ref(false)
// 修改數據(必須通過.value)
count.value++
message.value = 'Hello World'
// 在模板中自動解包,無需.value
// <view>{{ count }}</view>
特性説明:
- 支持所有數據類型(基本類型+對象)
- 必須通過
.value訪問和修改 - 模板中自動解包,簡化使用
- 適合管理單個值或需要整體替換的對象
1.2 reactive:對象的深度響應式
reactive用於創建深度響應式的對象或數組:
import { reactive } from 'vue'
// 創建響應式對象
const user = reactive({
name: '張三',
age: 25,
address: {
city: '北京',
street: '朝陽區'
}
})
// 直接修改屬性,無需.value
user.name = '李四'
user.address.city = '上海'
// 數組操作
const list = reactive([1, 2, 3])
list.push(4)
特性説明:
- 僅支持對象和數組類型
- 深度響應式,嵌套屬性也是響應式的
- 直接訪問屬性,無需
.value - 適合管理複雜對象或表單數據
1.3 ref vs reactive:如何選擇?
|
特性
|
ref
|
reactive
|
|
適用類型
|
所有類型
|
僅對象/數組
|
|
訪問方式
|
|
直接訪問屬性
|
|
深度響應
|
對象類型自動深度響應
|
深度響應
|
|
解構影響
|
解構後仍保持響應式
|
解構後丟失響應式
|
|
替換對象
|
支持整體替換
|
不支持整體替換
|
最佳實踐:
- 基本類型(數字、字符串、布爾值)使用
ref - 複雜對象或表單數據使用
reactive - 需要整體替換的對象用
ref包裝
1.4 toRefs:保持解構響應式
當需要解構reactive對象時,使用toRefs保持響應式:
import { reactive, toRefs } from 'vue'
const user = reactive({
name: '張三',
age: 25
})
// 解構會丟失響應式
const { name, age } = user // ❌ 錯誤
// 使用toRefs保持響應式
const { name, age } = toRefs(user) // ✅ 正確
name.value = '李四' // 觸發更新
二、組件通信:props與emit
2.1 父組件向子組件傳值(props)
父組件:
<template>
<view>
<child-component :message="parentMessage" :count="count"></child-component>
</view>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from '@/components/ChildComponent.vue'
const parentMessage = ref('Hello from parent')
const count = ref(0)
</script>
子組件:
<template>
<view>
<text>{{ message }}</text>
<text>{{ count }}</text>
</view>
</template>
<script setup>
defineProps({
message: {
type: String,
required: true,
default: '默認值'
},
count: {
type: Number,
default: 0
}
})
</script>
props驗證:
type:數據類型(String、Number、Boolean、Array、Object等)required:是否必填default:默認值validator:自定義驗證函數
2.2 子組件向父組件傳值(emit)
子組件:
<template>
<view>
<button @click="sendMessage">發送消息</button>
</view>
</template>
<script setup>
const emit = defineEmits(['updateMessage'])
const sendMessage = () => {
emit('updateMessage', 'Hello from child')
}
</script>
父組件:
<template>
<view>
<child-component @update-message="handleMessage"></child-component>
<text>{{ message }}</text>
</view>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from '@/components/ChildComponent.vue'
const message = ref('')
const handleMessage = (msg) => {
message.value = msg
}
</script>
emit事件命名規範:
- 使用kebab-case(短橫線命名)
- 推薦使用
update:xxx格式,配合v-model使用
2.3 v-model雙向綁定
子組件:
<template>
<view>
<input :value="modelValue" @input="updateValue" />
</view>
</template>
<script setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const updateValue = (event) => {
emit('update:modelValue', event.target.value)
}
</script>
父組件:
<template>
<view>
<child-component v-model="message"></child-component>
<text>{{ message }}</text>
</view>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from '@/components/ChildComponent.vue'
const message = ref('')
</script>
三、Pinia狀態管理
3.1 安裝與配置
uniapp內置了Pinia,無需額外安裝:
main.js配置:
import { createSSRApp } from 'vue'
import * as Pinia from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = Pinia.createPinia()
app.use(pinia)
return {
app,
Pinia // 必須返回Pinia
}
}
3.2 創建Store模塊
stores/user.js:
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: '',
isLogin: false
}),
getters: {
// 計算屬性
userName: (state) => state.userInfo?.name || '',
isAdmin: (state) => state.userInfo?.role === 'admin'
},
actions: {
// 同步操作
setUserInfo(userInfo) {
this.userInfo = userInfo
this.isLogin = true
},
// 異步操作
async login(account, password) {
try {
const res = await uni.request({
url: '/api/login',
method: 'POST',
data: { account, password }
})
this.setUserInfo(res.data.userInfo)
this.token = res.data.token
// 持久化存儲
uni.setStorageSync('token', this.token)
uni.setStorageSync('userInfo', this.userInfo)
return res.data
} catch (error) {
throw error
}
},
logout() {
this.userInfo = null
this.token = ''
this.isLogin = false
// 清除本地存儲
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
}
}
})
3.3 在組件中使用Store
<template>
<view>
<view v-if="userStore.isLogin">
<text>歡迎,{{ userStore.userName }}</text>
<button @click="userStore.logout">退出登錄</button>
</view>
<view v-else>
<button @click="login">登錄</button>
</view>
</view>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// 使用storeToRefs保持響應式
const { userInfo, isLogin } = storeToRefs(userStore)
const login = async () => {
try {
await userStore.login('admin', '123456')
uni.showToast({ title: '登錄成功' })
} catch (error) {
uni.showToast({ title: '登錄失敗', icon: 'error' })
}
}
</script>
3.4 跨頁面狀態共享
頁面A:
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const updateUser = () => {
userStore.setUserInfo({
name: '張三',
age: 25,
role: 'admin'
})
}
頁面B:
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 自動獲取更新後的數據
console.log(userStore.userInfo.name) // 張三
3.5 數據持久化
安裝持久化插件:
npm install pinia-plugin-persistedstate
配置持久化:
// stores/user.js
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: ''
}),
persist: {
key: 'user-store',
storage: {
getItem: uni.getStorageSync,
setItem: uni.setStorageSync,
removeItem: uni.removeStorageSync
},
paths: ['userInfo', 'token'] // 只持久化指定字段
}
})
四、鴻蒙平台特有配置
4.1 條件編譯處理平台差異
// 鴻蒙平台使用原生存儲
// #ifdef HARMONYOS
import preferences from '@ohos.data.preferences'
const saveData = async (key, value) => {
const pref = await preferences.getPreferences(key)
await pref.put(key, value)
await pref.flush()
}
// #endif
// 其他平台使用uni存儲
// #ifndef HARMONYOS
const saveData = (key, value) => {
uni.setStorageSync(key, value)
}
// #endif
4.2 鴻蒙原生狀態管理
uniapp在鴻蒙平台下會自動將Pinia狀態映射到鴻蒙原生狀態管理系統,實現更好的性能:
// 鴻蒙平台下,Pinia狀態會自動同步到ArkTS狀態管理
// 無需額外配置,uniapp會自動處理
五、實戰案例:購物車狀態管理
5.1 創建購物車Store
stores/cart.js:
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [], // 購物車商品列表
total: 0, // 總價
count: 0 // 商品數量
}),
getters: {
// 計算總價
totalPrice: (state) => {
return state.items.reduce((sum, item) => {
return sum + item.price * item.quantity
}, 0)
},
// 計算商品數量
totalCount: (state) => {
return state.items.reduce((sum, item) => {
return sum + item.quantity
}, 0)
}
},
actions: {
// 添加商品到購物車
addItem(product) {
const existingItem = this.items.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
this.items.push({
...product,
quantity: 1
})
}
this.updateTotal()
},
// 減少商品數量
decreaseItem(id) {
const item = this.items.find(item => item.id === id)
if (item && item.quantity > 1) {
item.quantity--
this.updateTotal()
}
},
// 刪除商品
removeItem(id) {
this.items = this.items.filter(item => item.id !== id)
this.updateTotal()
},
// 清空購物車
clearCart() {
this.items = []
this.updateTotal()
},
// 更新總價和數量
updateTotal() {
this.total = this.totalPrice
this.count = this.totalCount
}
}
})
5.2 在商品列表頁使用
<template>
<view>
<view v-for="product in productList" :key="product.id">
<text>{{ product.name }}</text>
<text>¥{{ product.price }}</text>
<button @click="addToCart(product)">加入購物車</button>
</view>
</view>
</template>
<script setup>
import { useCartStore } from '@/stores/cart'
const cartStore = useCartStore()
const productList = ref([
{ id: 1, name: '商品1', price: 99 },
{ id: 2, name: '商品2', price: 199 }
])
const addToCart = (product) => {
cartStore.addItem(product)
uni.showToast({ title: '添加成功' })
}
</script>
5.3 在購物車頁使用
<template>
<view>
<view v-for="item in cartStore.items" :key="item.id">
<text>{{ item.name }}</text>
<text>¥{{ item.price }}</text>
<text>數量:{{ item.quantity }}</text>
<button @click="cartStore.decreaseItem(item.id)">-</button>
<button @click="cartStore.addItem(item)">+</button>
<button @click="cartStore.removeItem(item.id)">刪除</button>
</view>
<view>
<text>總計:¥{{ cartStore.total }}</text>
<text>共{{ cartStore.count }}件商品</text>
</view>
<button @click="cartStore.clearCart">清空購物車</button>
</view>
</template>
<script setup>
import { useCartStore } from '@/stores/cart'
import { storeToRefs } from 'pinia'
const cartStore = useCartStore()
const { items, total, count } = storeToRefs(cartStore)
</script>
六、性能優化建議
6.1 避免過度響應式
// ❌ 不推薦:每個對象都創建響應式
const list = ref([
reactive({ id: 1, name: '商品1' }),
reactive({ id: 2, name: '商品2' })
])
// ✅ 推薦:只對需要響應式的字段創建響應式
const list = ref([
{ id: 1, name: '商品1' },
{ id: 2, name: '商品2' }
])
// 或者使用shallowRef/shallowReactive
import { shallowRef } from 'vue'
const largeData = shallowRef({ /* 大型對象 */ })
6.2 合理使用計算屬性
import { computed } from 'vue'
// 計算屬性緩存結果,避免重複計算
const filteredList = computed(() => {
return list.value.filter(item => item.price > 100)
})
// 複雜計算拆分
const totalPrice = computed(() => {
return list.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
6.3 避免內存泄漏
// 組件卸載時清理事件監聽
onUnmounted(() => {
uni.$off('customEvent')
})
// 定時器清理
const timer = setInterval(() => {
// do something
}, 1000)
onUnmounted(() => {
clearInterval(timer)
})
總結
通過本篇文章的學習,我們掌握了uniapp在鴻蒙平台下的數據綁定與狀態管理核心知識:
- 響應式基礎:
ref和reactive的區別與使用場景 - 組件通信:
props和emit實現父子組件數據傳遞 - 狀態管理:Pinia的安裝、配置和使用方法
- 實戰案例:購物車狀態管理的完整實現
- 性能優化:避免過度響應式、合理使用計算屬性
關鍵要點:
- 基本類型用
ref,複雜對象用reactive - 解構
reactive對象時使用toRefs保持響應式 - Pinia是Vue3推薦的狀態管理工具,支持同步和異步操作
- 鴻蒙平台下uniapp會自動處理狀態管理的跨平台適配
下一篇文章,我們將深入講解網絡請求與數據交互,包括uni.request的封裝、攔截器、錯誤處理、數據緩存等核心內容,幫助大家構建更健壯的應用。