当前位置: 首页 > news >正文

HarmonyOS StateStore 全局状态管理实战

一、开场白:多组件共享状态,你是不是一直在硬扛?

用 ArkUI 写页面,有个事儿特别头疼:好几个组件要用同一份数据,咋整?

官方给了 @State、@Prop、@Link、@Provide、@Consume 这些装饰器,听着挺多,用起来也凑合。但有个问题——状态跟组件绑得太死了

你想想,一个待办列表,新增按钮在一个组件里,删除按钮在另一个组件里,这俩还是兄弟关系。为了让它们共享数据,你得在父组件里维护一个 list,然后用 @Link 双向绑定,一通操作下来,代码乱成一锅粥。

更恶心的是,每个组件都得引入跟自己没啥关系的数据,纯属耦合。

StateStore 就是来救场的。它把状态从 UI 里抽出来,单独放一个全局仓库里。组件想用数据?去仓库拿。想改数据?给仓库发个消息就行。

就这么简单。


二、老办法有多难受?

先看看不用 StateStore 是啥情况。

假设你要做个待办列表,能新增、能删除。新增按钮在 ComponentA 里,删除按钮在 ComponentB 里,这俩组件是兄弟,共同的爹是 ParentComponent。

老办法咋搞?

父组件得维护一个 listDatas 数组,用 @Link 装饰器跟两个子组件双向绑定。新增和删除的逻辑分别在两个子组件里写,但这两个组件都得引入 listDatas 这个数据。

问题就来了:

  • ComponentA 明明只负责新增,却得知道 listDatas 的存在
  • ComponentB 明明只负责删除,也得知道 listDatas 的存在
  • 哪天你要改数据结构,三个组件都得跟着改

这耦合度,太高了。

我一开始就这么写的,后来改一次哭一次。


三、StateStore 咋解决的?

StateStore 的思路特别简单:把状态单独放一个仓库里,组件只管用,别管咋维护的

还是刚才那个待办列表,用 StateStore 之后:

  • listDatas 数据存在全局 Store 里
  • ComponentA 想新增?给 Store 发个 “add” 消息
  • ComponentB 想删除?给 Store 发个 “delete” 消息
  • 两个组件都从 Store 拿数据渲染 UI

看出来区别没?

组件跟组件之间没任何关系了,都只跟 Store 打交道。哪天你要改数据结构,改 Store 就行,组件不用动。

这就叫解耦。


四、StateStore 的核心就五个东西

别被官方文档吓到,StateStore 其实就五个概念,搞明白就能用了。

1. View(视图层)

View 就是用户看到的界面,里面是各种 UI 组件。

用户点按钮、滑列表,这些操作都在 View 里处理。View 不负责改数据,它只负责发消息给 Store,告诉 Store 发生了啥事。

2. Store(状态管理仓库)

Store 是核心,就是个全局仓库。

它对外提供两个方法:

  • getState():拿当前状态
  • dispatch(action):接收 UI 发来的消息,触发状态更新

你可以把 Store 理解成一个银行金库,数据都存在里面,想取钱(getState)或者存钱(dispatch)都得走它。

3. Action(事件描述对象)

Action 就是个消息对象,告诉 Store 发生了啥事。

它有两个属性:

  • type:事件类型,比如 “add”、“delete”、“update”
  • payload:具体数据,比如要新增的待办事项内容

举个例子:

// 我要新增一个待办事项constaddAction={type:'add',payload:'明天去超市'};

4. Reducer(状态刷新逻辑)

Reducer 是个函数,专门负责改数据。

Store 收到 Action 后,就调用 Reducer,说:“兄弟,有人发了个 add 消息,你处理一下”。Reducer 根据 type 判断要干啥,然后改状态。

constreducer=(state,action)=>{switch(action.type){case'add':state.list.push(action.payload);break;case'delete':state.list=state.list.filter(item=>item.id!==action.payload.id);break;}returnstate;};

5. Dispatch(事件分发方法)

Dispatch 就是 UI 跟 Store 之间的传话筒。

UI 里调用dispatch(action),把 Action 发给 Store,Store 再调用 Reducer 改数据。


五、运行原理,一张图搞定

看上图,流程就三步:

  1. 用户操作 UI→ View 调用dispatch(action)发消息
  2. Store 收到消息→ 调用 Reducer 处理
  3. Reducer 改数据→ 系统自动检测变化,刷新 UI

注意啊,StateStore 本身不管 UI 刷新,它只管改数据。UI 刷新靠的是 ArkUI 的@Observed@ObservedV2装饰器,这俩玩意儿能监听数据变化,自动触发 UI 更新。


六、实战:用 StateStore 写个备忘录

光说不练假把式,咱们直接上手写个备忘录应用。

功能就几个:

  • 新增待办
  • 删除待办
  • 标记完成
  • 显示未完成/已完成列表

第一步:定义数据

@ObservedV2装饰类,让系统能监听数据变化。

@ObservedV2exportclassTodoStoreModel{@TracetodoList:TodoItemData[]=[];@TraceisShow:boolean=false;addTaskTextInputValue:string='';@ComputedgetuncompletedTodoList():TodoItemData[]{returnthis.todoList.filter(item=>!item.selected);}@ComputedgetcompletedTodoList():TodoItemData[]{returnthis.todoList.filter(item=>item.selected);}}
@ObservedV2exportclassTodoItemData{id:number=0;@TracetaskDetail:string='';@Traceselected?:boolean;constructor(taskDetail:string,selected?:boolean,id?:number){this.id=id?id:Date.now();this.taskDetail=taskDetail;this.selected=selected;}}

这里有个坑,我踩过了,你们注意:

  • @ObservedV2装饰类,让类可被观察
  • @Trace装饰属性,让属性变化可被追踪
  • @Computed是计算属性,类似 Vue 的 computed

第二步:定义 Action

Action 就是事件类型,告诉 Store 有哪些消息可以发。

exportdefaultclassTodoListActions{staticgetTodoList:Action=StateStore.createAction('getTodoList');staticaddTodoList:Action=StateStore.createAction('addTodoList');staticdeleteTodoItem:Action=StateStore.createAction('deleteTodoItem');staticupdateTaskDetail:Action=StateStore.createAction('updateTaskDetail');staticcompleteTodoItem:Action=StateStore.createAction('completeTodoItem');}

StateStore.createAction()这个方法会创建一个 Action 对象,你只需要传个 type 就行。

第三步:写 Reducer

Reducer 是重头戏,所有状态更新逻辑都在这儿。

exportconsttodoReducer:Reducer<TodoStoreModel>=(state:TodoStoreModel,action:Action)=>{letGlobalContent=GlobalContext.getInstance();uiContext=GlobalContent.getUIContext()switch(action.type){caseTodoListActions.getTodoList.type:returnasync()=>{state.todoList=(awaitRdbUtil.getInstance(uiContext?.getHostContext()!)).query();};caseTodoListActions.addTodoList.type:if(state.addTaskTextInputValue===''){uiContext!.getPromptAction().showToast({message:$r('app.string.empty')});returnnull;}state.todoList.push(newTodoItemData(state.addTaskTextInputValue));state.isShow=false;state.addTaskTextInputValue='';break;caseTodoListActions.deleteTodoItem.type:// 删除逻辑break;caseTodoListActions.updateTaskDetail.type:// 更新任务详情逻辑break;caseTodoListActions.completeTodoItem.type:// 完成任务项逻辑break;}returnnull;};

写 Reducer 有个技巧:

  • 用 switch-case 按 type 区分不同事件
  • 每个 case 里写对应的状态更新逻辑
  • 改完状态直接返回,系统会自动检测变化

第四步:创建 Store

exportconstTODO_LIST_STORE_ID='todoListStore';exportconstTodoStore:Store<TodoStoreModel>=StateStore.createStore(TODO_LIST_STORE_ID,newTodoStoreModel(),todoReducer,[LogMiddleware]);

createStore方法四个参数:

  1. storeId:仓库 ID,随便取个名
  2. initialState:初始状态,new 一个 TodoStoreModel
  3. reducer:刚才写的 todoReducer
  4. middlewares:中间件,可选,后面再说

第五步:在 UI 里用

主页面 Index 组件:

@Entry@ComponentV2struct Index{@LocalviewModel:TodoStoreModel=TodoStore.getState();aboutToAppear():void{// dispatch 触发 GetTodoList 事件获取全量数据并更新状态TodoStore.dispatch(TodoListActions.getTodoList);}build(){Column(){if(this.viewModel.todoList.length>0){List({space:12}){if(this.viewModel.uncompletedTodoList.length>0){ListItemGroup({header:this.todayGroupHeader(),space:12}){ForEach(this.viewModel.uncompletedTodoList,(item:TodoItemData)=>{ListItem(){TodoItem({itemData:item});};},(item:TodoItemData)=>item.id.toString());};}}.width('100%').height('100%').layoutWeight(1);}}}}

关键就两行:

// 1. 用 getState() 拿状态@LocalviewModel:TodoStoreModel=TodoStore.getState();// 2. 用 dispatch() 发消息TodoStore.dispatch(TodoListActions.getTodoList);

子组件 TodoItem:

@ComponentV2exportstruct TodoItem{@Param@RequireitemData:TodoItemData;build(){Row({space:8}){Checkbox({name:'checkbox1',group:'checkboxGroup'}).select(this.itemData.selected).shape(CheckBoxShape.CIRCLE).onChange((_value)=>{// 子组件通过 dispatch 方法派发 CompleteTodoItem 事件改变全局状态TodoStore.dispatch(TodoListActions.completeTodoItem.setPayload({id:this.itemData.id,value:_value}));});}}}

看到没?子组件改状态也贼简单,就一行dispatch


七、子线程更新状态,有个坑要注意

HarmonyOS 有个限制:子线程不能直接改 UI 状态

这就尴尬了,你要是子线程里查数据库、调接口,拿到数据后想更新 UI,咋整?

StateStore 提供了SendableAction机制,专门解决这事儿。

定义可发送的数据

首先,子线程要传的数据得用@Sendable装饰:

@SendableexportclassToDoItemSendableimplementslang.ISendable{id:number;detail:string;selected:boolean;state:number;constructor(id:number,detail:string,selected:boolean=false){this.id=id;this.selected=selected;this.detail=detail;this.state=0;}}

子线程里发 Action

StateStore.createSendableAction创建可发送的 Action,然后用taskpool.Task.sendData发出去:

@ConcurrentasyncfunctionconcurrentUpdateProgress(context:Context,data:ToDoItemSendable[]):Promise<void>{try{letrdb=awaitRdbUtil.getInstance(context);constoriginalIds=rdb.getAllIds();consttoAdd=data.filter(todo=>!originalIds.some(id=>todo.id===id));consttoUpdate=data.filter(todo=>todo.state===0&&originalIds.indexOf(todo.id)>-1);consttoDelete=originalIds.filter(id=>!data.some(todo=>todo.id===id));// 发送 setTotal 事件设置进度条总数taskpool.Task.sendData(StateStore.createSendableAction(TODO_LIST_STORE_ID,TodoListActions.setTotal.type,toAdd.length+toUpdate.length+toDelete.length));for(consttodooftoAdd){rdb.inset(todo);awaitsleep(500);// 发送更新进度条事件 updateProgresstaskpool.Task.sendData(StateStore.createSendableAction(TODO_LIST_STORE_ID,TodoListActions.updateProgress.type,todo.id));}}catch(err){console.error(`${err.message}\n${err.stack}`);returnundefined;}}

主线程里收 Action

主线程用onReceiveData接收,然后用StateStore.receiveSendableAction执行:

exportasyncfunctionsyncDatabase(){try{consttodos:TodoItemData[]=TodoStore.getState().todoList;constToBeSynced=todos.map(item=>item.toDoItemSendable);lettask:taskpool.Task=newtaskpool.Task(concurrentUpdateProgress,uiContext?.getHostContext()!,ToBeSynced);task.onReceiveData((data:SendableAction)=>{// 用 receiveSendableAction 方法触发子线程发送的 Action 刷新状态StateStore.receiveSendableAction(data);});awaittaskpool.execute(task);TodoStore.dispatch(TodoListActions.clearProgress);}catch(err){console.error(`${err.message}\n${err.stack}`);}}

Reducer 处理

Reducer 里正常处理就行:

caseTodoListActions.updateProgress.type:letitem=state.syncTodoList.find(item=>item.id===action.payload);item?.updateState(1);state.progress.value++;break;caseTodoListActions.setTotal.type:state.syncTodoList=state.todoList.filter(item=>item.state===0);state.progress.total=action.payload;break;

八、中间件:在状态更新前后插点私货

有时候你想在状态更新前后干点别的事,比如:

  • 记录日志
  • 权限校验
  • 数据埋点

这些逻辑要是直接写在 Reducer 里,代码就乱了。

中间件就是干这个的。它能在 Action 分发到 Reducer 之前和之后,执行自定义逻辑。

定义中间件

中间件要实现beforeActionafterAction两个钩子:

exportclassMiddlewareInstance<T>extendsMiddleware<T>{beforeAction:MiddlewareFuncType<T>;afterAction:MiddlewareFuncType<T>;constructor(beforeAction:MiddlewareFuncType<T>,afterAction:MiddlewareFuncType<T>){super();this.beforeAction=beforeAction;this.afterAction=afterAction;}}exportconstLogMiddleware=newMiddlewareInstance<TodoStoreModel>((state:TodoStoreModel,action:Action)=>{hilog.info(0x0000,'StateStoreSample',`logMiddleware-before1:${JSON.stringify(state.todoList)},${action.type}`);returnMiddlewareStatus.NEXT;},(state:TodoStoreModel)=>{hilog.info(0x0000,'StateStoreSample',`logMiddleware-after:${JSON.stringify(state.todoList)}`);returnMiddlewareStatus.NEXT;});

注册中间件

创建 Store 的时候传进去就行:

exportconstTodoStore:Store<TodoStoreModel>=StateStore.createStore(TODO_LIST_STORE_ID,newTodoStoreModel(),todoReducer,[LogMiddleware]);

注册之后,每次状态更新前后都会自动执行中间件的逻辑。


九、总结一下

StateStore 就干一件事:把状态跟 UI 剥离开

  • 状态存在 Store 里,组件只管用
  • 想改状态?发个 Action 给 Store
  • Reducer 集中处理所有状态更新逻辑
  • 子线程更新用 SendableAction
  • 要扩展功能?用中间件

这么搞下来,代码结构清晰,多个组件共享状态也简单,维护起来不头疼。

我用了之后,最大的感受就是:早该这么写了

http://www.jsqmd.com/news/642334/

相关文章:

  • 终极指南:如何免费解锁Cursor AI编辑器的完整Pro功能
  • Oracle监听程序配置全攻略:从ORA-12541错误到完美解决(附PLSQL连接技巧)
  • 双叶家具联系方式查询:在山西大同选购实木家具时如何通过官方渠道联系与实地探访 - 品牌推荐
  • **发散创新:基于 OpenTelemetry 的分布式链路追踪实战与性能
  • 网盘直链下载助手:八大网盘一键解析,告别限速烦恼的终极解决方案
  • 无线充电电动牙刷设计解析:瑞萨R7F0C807与PWM驱动技术
  • 性能测试项目中遇到的20个问题以及解决方法
  • KAWASAKI 50999-2145R10控制卡
  • Python学习日志(二):基础语法
  • 教你怎样搭建自动化测试框架?
  • 精准力控安全夹持,力控夹爪厂家品控与售后体系全解析 - 品牌2026
  • 每日一题:.NET 性能优化常用手段有哪些?
  • 璀璨时代楼盘联系方式查询指南:结合区域发展与居住品质的客观信息参考 - 品牌推荐
  • 2026年精密夹爪品牌推荐:精密夹爪核心指标与品质管控标准解读 - 品牌2026
  • 区块链分片算法突破:MLGO信任场重塑物联网,Kafka06-进阶-尚硅谷。
  • 终极免费音频解密工具:3分钟解锁QQ音乐加密文件实现跨平台播放
  • 精密装配力控保障:2026年优质供应商甄选与供货稳定性核查 - 品牌2026
  • 告别繁琐!OpenClaw Windows 可视化一键部署安装教程
  • 客服机器人回答错误可自动撤回?智能 Agent 功能详解 + 消息撤回,发错答案快速补救?
  • 6.1 加权方法:等权、IC加权、风险平价
  • 2026年SCI论文AI率超标怎么办?这4款降AI工具实测通过率最高
  • 河南精铸工匠不锈钢有限公司电话查询:获取官方联系途径的指南与商业合作注意事项 - 品牌推荐
  • 2026年电爪品牌推荐:电爪品牌实力精选与品控标准测评 - 品牌2026
  • 玄域靶场越权系列第1关实战复盘
  • 10-15万家庭混动SUV安全性能实证研究报告
  • 如何在jupyter中实现qutip输出电路示意图
  • 刘艳伟律师联系方式:在郑州寻求建设工程与房地产领域专业法律支持时的联系途径与初步沟通建议 - 品牌推荐
  • **发散创新:基于RBAC模型的开源权限管理系统设计与实现**在现代软件架构中,权限控制是系统安全的核
  • DearPyGui内置的‘开发者工具箱’有多强?手把手教你用Style Editor和Metrics打造专属UI
  • 拼多多爬虫终极指南:3步获取电商平台真实数据