在类Web开发范式(.hml/.css/.js)中,状态管理主要通过JavaScript的数据绑定和事件机制实现,核心是维护数据与UI的一致性。由于类Web范式缺乏声明式范式(ArkTS)的@State等装饰器自动关联状态,需要手动设计状态流转逻辑。以下是具体实现方式和最佳实践:
一、基础状态管理:组件内状态
对于单个页面(组件)的状态管理,可直接利用鸿蒙类Web范式的数据绑定机制,通过data定义状态,setData更新状态并触发UI刷新。
1. 基本用法
<!-- index.hml -->
<div class="container">
<text>{
{ count }}</text>
<button onclick="increment">+1</button>
</div>
// index.js
export default {
// 定义初始状态
data: {
count: 0
},
// 方法:更新状态(必须通过setData触发UI刷新)
increment() {
// 错误方式:直接修改不会触发UI更新
// this.data.count++
// 正确方式:通过setData更新
this.setData({
count: this.data.count + 1
});
}
}
2. 复杂状态处理
对于对象/数组类型的状态,需注意setData的部分更新特性(仅更新变化的字段,避免全量替换):
// 初始状态
data: {
user: {
name: "鸿蒙", age: 3 },
list: [1, 2, 3]
},
// 更新对象字段(推荐:仅更新变化的属性)
updateUserName() {
this.setData({
"user.name": "HarmonyOS" // 部分更新,性能更优
});
},
// 更新数组元素
updateListItem() {
this.setData({
"list[0]": 100 // 直接定位索引更新
});
}
二、跨组件/页面状态管理
当状态需要在多个组件(如父子组件、兄弟组件)或页面间共享时,需通过事件传递、全局变量或发布-订阅模式实现。
1. 父子组件通信(事件+参数)
- 父组件通过
props向子组件传递状态; - 子组件通过
$emit触发事件,父组件监听事件更新状态。
子组件(child.hml):
<!-- 子组件接收父组件的props -->
<text>{
{ parentMsg }}</text>
<button onclick="sendToParent">向父组件传值</button>
// child.js
export default {
props: ["parentMsg"], // 声明接收的属性
sendToParent() {
// 触发事件,传递数据给父组件
this.$emit("childEvent", "来自子组件的消息");
}
}
父组件(parent.hml):
<!-- 父组件传递props,并监听子组件事件 -->
<child
parentMsg="{
{ parentMessage }}"
onchildEvent="handleChildEvent"
></child>
<text>{
{ childMsg }}</text>
// parent.js
export default {
data: {
parentMessage: "来自父组件的消息",
childMsg: ""
},
handleChildEvent(e) {
// 接收子组件传递的数据并更新状态
this.setData({
childMsg: e.detail // e.detail为子组件传递的参数
});
}
}
2. 非父子组件/跨页面通信
对于无直接关系的组件或页面,可通过以下方式共享状态:
(1)全局变量(简单场景)
利用app.js的全局对象存储共享状态(适合小型应用):
// app.js(在EntryAbility的onCreate中初始化)
export default {
onCreate() {
// 定义全局状态
this.globalData = {
theme: "light",
userInfo: null
};
}
};
// 页面A中更新全局状态
import app from '@system.app';
export default {
setGlobalTheme() {
app.getContext().globalData.theme = "dark";
}
};
// 页面B中读取全局状态
import app from '@system.app';
export default {
onShow() {
// 读取全局状态并更新本地UI
this.setData({
currentTheme: app.getContext().globalData.theme
});
}
};
(2)发布-订阅模式(Pub/Sub,推荐)
通过事件总线(EventBus)实现解耦的状态通信,适合中大型应用:
// 新建eventBus.js(事件总线工具)
const events = {
};
export default {
// 订阅事件
on(eventName, callback) {
if (!events[eventName]) {
events[eventName] = [];
}
events[eventName].push(callback);
},
// 发布事件
emit(eventName, data) {
if (events[eventName]) {
events[eventName].forEach(callback => callback(data));
}
},
// 取消订阅
off(eventName, callback) {
if (events[eventName]) {
events[eventName] = events[eventName].filter(cb => cb !== callback);
}
}
};
组件A(发布者):
import eventBus from '../common/eventBus.js';
export default {
sendMessage() {
// 发布事件,传递状态
eventBus.emit("userLogin", {
name: "鸿蒙用户" });
}
};
组件B(订阅者):
import eventBus from '../common/eventBus.js';
export default {
data: {
loginUser: null
},
onInit() {
// 订阅事件,更新本地状态
this.loginCallback = (user) => {
this.setData({
loginUser: user });
};
eventBus.on("userLogin", this.loginCallback);
},
onDestroy() {
// 页面销毁时取消订阅,避免内存泄漏
eventBus.off("userLogin", this.loginCallback);
}
};
三、状态管理最佳实践
状态最小化原则
只将需要共享的状态提升到全局或父组件,避免冗余状态(如局部UI状态应放在组件内部)。避免频繁setData
合并多次状态更新(如this.setData({a:1, b:2})而非两次调用),减少UI渲染次数。状态变更可追溯
复杂状态更新建议封装为方法(如updateUserInfo()),而非直接在事件回调中修改,便于调试和维护。清理订阅关系
页面/组件销毁时,务必取消事件订阅、清除定时器,避免内存泄漏(参考上述eventBus.off示例)。大型应用建议
若应用复杂度高(如多页面共享大量状态),可引入类Redux的状态管理模式(通过单例store集中管理状态,用action触发更新),但需手动实现(类Web范式无官方状态库)。
总结
类Web开发范式的状态管理依赖手动数据绑定和事件机制,核心是通过setData触发UI更新,并根据组件关系选择合适的通信方式(props/事件总线/全局变量)。相比声明式范式的自动状态关联,类Web范式需要更多手动代码维护状态一致性,适合中小型应用或简单场景。若状态逻辑复杂,建议考虑迁移到声明式范式(ArkTS),利用@State @Link等装饰器简化状态管理。