数据绑定与状态管理
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提供了完整的数据绑定和状态管理能力,从简单的组件内状态到复杂的全局状态管理都有对应的解决方案。在实际开发中,应根据应用的复杂度选择合适的状态管理方式,并遵循最佳实践,以确保应用的可维护性和性能。