一、开场白多组件共享状态你是不是一直在硬扛用 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){caseadd:state.list.push(action.payload);break;casedelete:state.liststate.list.filter(itemitem.id!action.payload.id);break;}returnstate;};5. Dispatch事件分发方法Dispatch 就是 UI 跟 Store 之间的传话筒。UI 里调用dispatch(action)把 Action 发给 StoreStore 再调用 Reducer 改数据。五、运行原理一张图搞定看上图流程就三步用户操作 UI→ View 调用dispatch(action)发消息Store 收到消息→ 调用 Reducer 处理Reducer 改数据→ 系统自动检测变化刷新 UI注意啊StateStore 本身不管 UI 刷新它只管改数据。UI 刷新靠的是 ArkUI 的Observed和ObservedV2装饰器这俩玩意儿能监听数据变化自动触发 UI 更新。六、实战用 StateStore 写个备忘录光说不练假把式咱们直接上手写个备忘录应用。功能就几个新增待办删除待办标记完成显示未完成/已完成列表第一步定义数据用ObservedV2装饰类让系统能监听数据变化。ObservedV2exportclassTodoStoreModel{TracetodoList:TodoItemData[][];TraceisShow:booleanfalse;addTaskTextInputValue:string;ComputedgetuncompletedTodoList():TodoItemData[]{returnthis.todoList.filter(item!item.selected);}ComputedgetcompletedTodoList():TodoItemData[]{returnthis.todoList.filter(itemitem.selected);}}ObservedV2exportclassTodoItemData{id:number0;TracetaskDetail:string;Traceselected?:boolean;constructor(taskDetail:string,selected?:boolean,id?:number){this.idid?id:Date.now();this.taskDetailtaskDetail;this.selectedselected;}}这里有个坑我踩过了你们注意ObservedV2装饰类让类可被观察Trace装饰属性让属性变化可被追踪Computed是计算属性类似 Vue 的 computed第二步定义 ActionAction 就是事件类型告诉 Store 有哪些消息可以发。exportdefaultclassTodoListActions{staticgetTodoList:ActionStateStore.createAction(getTodoList);staticaddTodoList:ActionStateStore.createAction(addTodoList);staticdeleteTodoItem:ActionStateStore.createAction(deleteTodoItem);staticupdateTaskDetail:ActionStateStore.createAction(updateTaskDetail);staticcompleteTodoItem:ActionStateStore.createAction(completeTodoItem);}StateStore.createAction()这个方法会创建一个 Action 对象你只需要传个 type 就行。第三步写 ReducerReducer 是重头戏所有状态更新逻辑都在这儿。exportconsttodoReducer:ReducerTodoStoreModel(state:TodoStoreModel,action:Action){letGlobalContentGlobalContext.getInstance();uiContextGlobalContent.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.isShowfalse;state.addTaskTextInputValue;break;caseTodoListActions.deleteTodoItem.type:// 删除逻辑break;caseTodoListActions.updateTaskDetail.type:// 更新任务详情逻辑break;caseTodoListActions.completeTodoItem.type:// 完成任务项逻辑break;}returnnull;};写 Reducer 有个技巧用 switch-case 按 type 区分不同事件每个 case 里写对应的状态更新逻辑改完状态直接返回系统会自动检测变化第四步创建 StoreexportconstTODO_LIST_STORE_IDtodoListStore;exportconstTodoStore:StoreTodoStoreModelStateStore.createStore(TODO_LIST_STORE_ID,newTodoStoreModel(),todoReducer,[LogMiddleware]);createStore方法四个参数storeId仓库 ID随便取个名initialState初始状态new 一个 TodoStoreModelreducer刚才写的 todoReducermiddlewares中间件可选后面再说第五步在 UI 里用主页面 Index 组件EntryComponentV2struct Index{LocalviewModel:TodoStoreModelTodoStore.getState();aboutToAppear():void{// dispatch 触发 GetTodoList 事件获取全量数据并更新状态TodoStore.dispatch(TodoListActions.getTodoList);}build(){Column(){if(this.viewModel.todoList.length0){List({space:12}){if(this.viewModel.uncompletedTodoList.length0){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:TodoStoreModelTodoStore.getState();// 2. 用 dispatch() 发消息TodoStore.dispatch(TodoListActions.getTodoList);子组件 TodoItemComponentV2exportstruct TodoItem{ParamRequireitemData: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:booleanfalse){this.idid;this.selectedselected;this.detaildetail;this.state0;}}子线程里发 Action用StateStore.createSendableAction创建可发送的 Action然后用taskpool.Task.sendData发出去ConcurrentasyncfunctionconcurrentUpdateProgress(context:Context,data:ToDoItemSendable[]):Promisevoid{try{letrdbawaitRdbUtil.getInstance(context);constoriginalIdsrdb.getAllIds();consttoAdddata.filter(todo!originalIds.some(idtodo.idid));consttoUpdatedata.filter(todotodo.state0originalIds.indexOf(todo.id)-1);consttoDeleteoriginalIds.filter(id!data.some(todotodo.idid));// 发送 setTotal 事件设置进度条总数taskpool.Task.sendData(StateStore.createSendableAction(TODO_LIST_STORE_ID,TodoListActions.setTotal.type,toAdd.lengthtoUpdate.lengthtoDelete.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;constToBeSyncedtodos.map(itemitem.toDoItemSendable);lettask:taskpool.Tasknewtaskpool.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:letitemstate.syncTodoList.find(itemitem.idaction.payload);item?.updateState(1);state.progress.value;break;caseTodoListActions.setTotal.type:state.syncTodoListstate.todoList.filter(itemitem.state0);state.progress.totalaction.payload;break;八、中间件在状态更新前后插点私货有时候你想在状态更新前后干点别的事比如记录日志权限校验数据埋点这些逻辑要是直接写在 Reducer 里代码就乱了。中间件就是干这个的。它能在 Action 分发到 Reducer 之前和之后执行自定义逻辑。定义中间件中间件要实现beforeAction和afterAction两个钩子exportclassMiddlewareInstanceTextendsMiddlewareT{beforeAction:MiddlewareFuncTypeT;afterAction:MiddlewareFuncTypeT;constructor(beforeAction:MiddlewareFuncTypeT,afterAction:MiddlewareFuncTypeT){super();this.beforeActionbeforeAction;this.afterActionafterAction;}}exportconstLogMiddlewarenewMiddlewareInstanceTodoStoreModel((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:StoreTodoStoreModelStateStore.createStore(TODO_LIST_STORE_ID,newTodoStoreModel(),todoReducer,[LogMiddleware]);注册之后每次状态更新前后都会自动执行中间件的逻辑。九、总结一下StateStore 就干一件事把状态跟 UI 剥离开。状态存在 Store 里组件只管用想改状态发个 Action 给 StoreReducer 集中处理所有状态更新逻辑子线程更新用 SendableAction要扩展功能用中间件这么搞下来代码结构清晰多个组件共享状态也简单维护起来不头疼。我用了之后最大的感受就是早该这么写了。