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在鸿蒙平台下的数据绑定与状态管理核心知识:

  1. 响应式基础refreactive的区别与使用场景
  2. 组件通信propsemit实现父子组件数据传递
  3. 状态管理:Pinia的安装、配置和使用方法
  4. 实战案例:购物车状态管理的完整实现
  5. 性能优化:避免过度响应式、合理使用计算属性

关键要点

  • 基本类型用ref,复杂对象用reactive
  • 解构reactive对象时使用toRefs保持响应式
  • Pinia是Vue3推荐的状态管理工具,支持同步和异步操作
  • 鸿蒙平台下uniapp会自动处理状态管理的跨平台适配

下一篇文章,我们将深入讲解网络请求与数据交互,包括uni.request的封装、拦截器、错误处理、数据缓存等核心内容,帮助大家构建更健壮的应用。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐