uniapp开发鸿蒙:数据绑定与状态管理实战
响应式基础ref和reactive的区别与使用场景组件通信props和emit实现父子组件数据传递状态管理:Pinia的安装、配置和使用方法实战案例:购物车状态管理的完整实现性能优化:避免过度响应式、合理使用计算属性关键要点基本类型用ref,复杂对象用reactive解构reactive对象时使用toRefs保持响应式Pinia是Vue3推荐的状态管理工具,支持同步和异步操作鸿蒙平台下uniapp
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 |
|---|---|---|
| 适用类型 | 所有类型 | 仅对象/数组 |
| 访问方式 | .value |
直接访问属性 |
| 深度响应 | 对象类型自动深度响应 | 深度响应 |
| 解构影响 | 解构后仍保持响应式 | 解构后丢失响应式 |
| 替换对象 | 支持整体替换 | 不支持整体替换 |
最佳实践:
- 基本类型(数字、字符串、布尔值)使用
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的封装、拦截器、错误处理、数据缓存等核心内容,帮助大家构建更健壮的应用。
更多推荐




所有评论(0)