OpenHarmony 全局全局状态管理 AppStorage + PersistentStorage 封装(API23)
摘要
ArkUI 提供 AppStorage 内存全局状态、PersistentStorage 持久绑定存储两套全局状态方案,原生零散使用存在问题:全局 key 分散、无统一类型约束、状态变更监听冗余、持久化绑定重复编写、登录 / 深色模式 / 用户信息多状态混杂。本文封装统一全局状态管理类 GlobalStore,集中管理全部全局变量、提供类型化读写、统一持久化绑定、批量监听状态变化、封装登录退出清空逻辑,联动前文 Preferences 持久存储工具。API23 重构 AppStorage 响应式监听、持久化同步机制、页面销毁自动解绑监听,修复多页面同时监听触发多次回调、持久化同步延迟、页面销毁监听残留内存泄漏等问题。
关键词
OpenHarmony;ArkTS;API23;AppStorage;PersistentStorage;全局状态;状态管理;持久化状态
一、引言
1.1 原生全局状态开发痛点
- 全局状态 Key 字符串硬编码分散在各个页面,修改易漏改报错;
- 无 TS 类型约束,存取全局变量全为 any,无代码提示易传错值;
- 深色模式、登录状态、用户信息等需要持久化,每个页面重复写 PersistentStorage 绑定;
- 多页面同时监听同一状态变更,多次重复执行回调;
- 退出登录时需要逐个清空所有全局变量,代码繁琐;
- 页面销毁未移除状态监听,后台持续触发回调造成页面报错。
1.2 两套核心 API 区分
- AppStorage:内存全局状态,整个应用单例共享,重启 APP 丢失;页面实时响应 @State 同等响应式更新 UI。
- PersistentStorage:桥接 AppStorage + Preferences,AppStorage 变量变更自动写入本地持久化,启动自动读取缓存回填。
API23 状态管理核心升级:
- PersistentStorage 同步写入优化,状态修改立刻落盘,不会重启丢失;
- watch 监听支持页面生命周期自动解绑,规避内存泄漏;
- 全局状态读写增加类型校验,非法赋值抛出日志警告;
- 多页面同变量监听去重,避免同一状态多次触发回调;
- 区分临时内存状态与持久化状态,分开管理互不干扰。
二、全局状态常量与类型定义 model/GlobalState.ets
ets
// 全局状态唯一Key常量,统一管理
export const GLOBAL_KEY = {
// 持久化状态(重启保留)
IS_LOGIN: "isLogin",
TOKEN: "token",
DARK_MODE: "darkMode",
LAST_ACCOUNT: "lastAccount",
// 临时内存状态(重启清空)
USER_INFO: "userInfo",
CUR_PAGE_INDEX: "curPageIndex"
}
// 用户信息类型
export interface UserInfo {
userId: number
userName: string
avatar: string
}
// 全局状态完整类型约束
export interface GlobalStateType {
[GLOBAL_KEY.IS_LOGIN]: boolean
[GLOBAL_KEY.TOKEN]: string
[GLOBAL_KEY.DARK_MODE]: boolean
[GLOBAL_KEY.LAST_ACCOUNT]: string
[GLOBAL_KEY.USER_INFO]: UserInfo | null
[GLOBAL_KEY.CUR_PAGE_INDEX]: number
}
三、全局状态管理封装 utils/global_store.ets
ets
import AppStorage from '@ohos.arkui.state.AppStorage'
import PersistentStorage from '@ohos.arkui.state.PersistentStorage'
import { GLOBAL_KEY, GlobalStateType, UserInfo } from '../model/GlobalState'
import LogUtil from './log_util'
class GlobalStore {
private static instance: GlobalStore
// 存储页面监听映射,页面销毁批量解绑
private watchMap: Map<string, () => void> = new Map()
static getInstance(): GlobalStore {
if (!GlobalStore.instance) {
GlobalStore.instance = new GlobalStore()
}
return GlobalStore.instance
}
// 初始化持久化绑定(EntryAbility onCreate全局执行一次)
initPersistentBind() {
// 需要持久化的变量绑定本地Preferences
PersistentStorage.link(GLOBAL_KEY.IS_LOGIN, false)
PersistentStorage.link(GLOBAL_KEY.TOKEN, "")
PersistentStorage.link(GLOBAL_KEY.DARK_MODE, false)
PersistentStorage.link(GLOBAL_KEY.LAST_ACCOUNT, "")
LogUtil.info("GlobalStore", "持久化全局状态绑定完成")
}
// 泛型读取全局状态
get<T extends keyof GlobalStateType>(key: T): GlobalStateType[T] {
return AppStorage.Get<GlobalStateType[T]>(key)
}
// 泛型写入全局状态
set<T extends keyof GlobalStateType>(key: T, value: GlobalStateType[T]) {
const oldVal = this.get(key)
AppStorage.SetOrCreate(key, value)
LogUtil.debug("GlobalStore", `状态变更 key:${key} 旧值:${oldVal} 新值:${value}`)
}
// 监听状态变化,返回解绑函数
watch<T extends keyof GlobalStateType>(
key: T,
callback: (newVal: GlobalStateType[T], oldVal: GlobalStateType[T]) => void
): () => void {
const unWatch = AppStorage.Watch(key, callback)
// 存入监听池,页面销毁统一释放
const watchId = `${key}_${Date.now()}`
this.watchMap.set(watchId, unWatch)
return () => {
unWatch()
this.watchMap.delete(watchId)
}
}
// 批量移除当前页面所有监听(页面aboutToDisappear调用)
clearPageWatch() {
this.watchMap.forEach(unWatch => unWatch())
this.watchMap.clear()
LogUtil.debug("GlobalStore", "页面全局状态监听全部解绑")
}
// 登录赋值全套全局状态
setLogin(token: string, user: UserInfo, account: string) {
this.set(GLOBAL_KEY.IS_LOGIN, true)
this.set(GLOBAL_KEY.TOKEN, token)
this.set(GLOBAL_KEY.USER_INFO, user)
this.set(GLOBAL_KEY.LAST_ACCOUNT, account)
}
// 退出登录清空全部登录相关状态
logoutClear() {
this.set(GLOBAL_KEY.IS_LOGIN, false)
this.set(GLOBAL_KEY.TOKEN, "")
this.set(GLOBAL_KEY.USER_INFO, null)
LogUtil.info("GlobalStore", "执行退出登录,清空用户全局状态")
}
// 切换深色模式
toggleDarkMode(val: boolean) {
this.set(GLOBAL_KEY.DARK_MODE, val)
}
}
export default GlobalStore.getInstance()
四、配套初始化:EntryAbility 全局一次性绑定持久化
ets
import UIAbility from '@ohos.app.ability.UIAbility'
import GlobalStore from '../utils/global_store'
import LogUtil, { LogLevel } from '../utils/log_util'
import FileUtil from '../utils/file_util'
export default class EntryAbility extends UIAbility {
onCreate() {
const ctx = this.context
// 初始化底层工具上下文
FileUtil.setContext(ctx)
LogUtil.setContext(ctx)
// 生产环境屏蔽调试日志
LogUtil.setLevel(LogLevel.INFO)
// 全局持久化状态绑定,应用启动仅执行一次
GlobalStore.initPersistentBind()
}
}
五、页面实战调用示例
5.1 设置页:深色模式切换监听
ets
import GlobalStore from '../utils/global_store'
import { GLOBAL_KEY } from '../model/GlobalState'
@Entry
@Component
struct SettingPage {
@State dark: boolean = false
unWatch?: () => void
aboutToAppear() {
// 读取全局深色模式
this.dark = GlobalStore.get(GLOBAL_KEY.DARK_MODE)
// 监听全局状态变化,UI自动同步
this.unWatch = GlobalStore.watch(GLOBAL_KEY.DARK_MODE, (newVal) => {
this.dark = newVal
})
}
// 切换全局深色模式
changeDark(val: boolean) {
GlobalStore.toggleDarkMode(val)
}
build() {
Column() {
Row() {
Text("深色模式").layoutWeight(1).fontSize(18)
Toggle({ isOn: this.dark })
.onChange((v: boolean) => this.changeDark(v))
}
}
.width("100%")
.height("100%")
.backgroundColor(this.dark ? "#121212" : "#ffffff")
}
aboutToDisappear() {
// 解绑当前页面所有状态监听,防止内存泄漏
if (this.unWatch) this.unWatch()
GlobalStore.clearPageWatch()
}
}
5.2 登录页面:登录写入全局状态
ets
import GlobalStore from '../utils/global_store'
import RouterUtil from '../utils/router_util'
import { PAGE_ROUTE } from '../model/Constant'
import HttpUtil from '../utils/http_util'
@Entry
@Component
struct LoginPage {
@State account: string = ""
@State pwd: string = ""
async submitLogin() {
const res = await HttpUtil.post<{token:string, user:UserInfo}>("/login", {
username: this.account,
password: this.pwd
})
if (!res) return
// 统一写入登录全套全局状态
GlobalStore.setLogin(res.data.token, res.data.user, this.account)
// 跳转首页清空登录栈
RouterUtil.clearToIndex()
}
build() {
Column() {
TextInput({text:this.account, placeholder:"账号"})
TextInput({text:this.pwd, placeholder:"密码", type:InputType.Password})
Button("登录").onClick(()=>this.submitLogin())
}
}
}
5.3 个人中心页:退出登录清空全局状态
ets
import GlobalStore from '../utils/global_store'
import RouterUtil from '../utils/router_util'
import DialogUtil from '../utils/dialog_util'
@Entry
@Component
struct MinePage {
build() {
Column() {
Button("退出登录")
.onClick(() => {
DialogUtil.confirm({content:"确定退出登录吗?"}, ()=>{
// 清空登录全局状态
GlobalStore.logoutClear()
// 跳转登录页清空路由栈
RouterUtil.clearAndLogin()
})
})
}
}
}
5.4 首页判断登录状态拦截路由
ets
import GlobalStore from '../utils/global_store'
import { GLOBAL_KEY } from '../model/GlobalState'
import RouterUtil from '../utils/router_util'
// 跳转个人中心工具内置登录判断复用
function jumpMine() {
const isLogin = GlobalStore.get(GLOBAL_KEY.IS_LOGIN)
if (!isLogin) {
DialogUtil.alert({content:"请先登录"})
RouterUtil.push(PAGE_ROUTE.LOGIN)
return
}
RouterUtil.push(PAGE_ROUTE.MINE, {}, true)
}
六、全局状态开发编码规范
6.1 Key 管理规范
- 所有全局变量统一存入
GLOBAL_KEY常量,禁止页面手写字符串 key; - 区分持久化 / 临时内存状态分组,便于维护;
- 新增全局状态必须同步更新
GlobalStateType类型定义,保证类型安全。
6.2 持久化绑定规范
- PersistentStorage.link 仅在 EntryAbility.onCreate 全局执行一次;
- 仅用户配置、登录凭证等需要重启保留的变量开启持久化;
- 页面临时页面索引、弹窗临时标记等仅放内存,不持久化。
6.3 监听生命周期规范
- 页面使用 watch 监听必须保存解绑函数;
- aboutToDisappear 执行解绑,调用 clearPageWatch 清除全部监听;
- 高频页面避免过多 watch 监听,减少回调执行开销。
6.4 登录状态规范
- 登录统一调用 setLogin 批量赋值 token、用户信息、登录标记;
- 退出固定调用 logoutClear 一键清空全部登录相关状态;
- 路由、弹窗、接口请求全部读取 GlobalStore 判断登录,统一数据源。
6.5 UI 渲染规范
- 页面 @State 变量从 GlobalStore 读取初始值,搭配 watch 同步更新;
- 禁止页面直接操作 AppStorage 原生 API,全部通过 GlobalStore 封装方法读写。
七、高频问题与解决方案
问题 1:重启 APP 深色模式、登录状态丢失 解决:在 EntryAbility 初始化执行 PersistentStorage.link 绑定对应 key,工具内部自动持久化同步。
问题 2:页面销毁后状态变更回调持续执行,页面变量报错 解决:页面销毁调用解绑函数 clearPageWatch 移除所有 watch 监听。
问题 3:多处页面修改 token,代码分散难维护 解决:统一封装 setLogin/logoutClear 方法,集中管理登录相关全局变量。
问题 4:存取全局变量无代码提示,容易传错类型 解决:使用泛型 get<T>/set<T>,配套 GlobalStateType 完整类型约束。
问题 5:升级 API23 后 PersistentStorage 同步延迟,修改后立刻读取还是旧值 解决:API23 优化自动同步机制,link 绑定后 set 写入立即落盘,无需手动 flush。
八、总结
GlobalStore 封装 ArkUI AppStorage + PersistentStorage 全套全局状态能力,统一管理全局 Key、类型约束、持久化绑定、状态监听、登录状态批量操作,解决原生全局状态硬编码、无类型、监听泄漏、持久化重复绑定等工程化痛点。 全局仅在 EntryAbility 一次性初始化持久化绑定,所有页面统一通过工具类读写状态,完美联动前文路由拦截、登录校验、深色模式、Preferences 存储整套底层工具,是鸿蒙项目跨页面共享数据标准解决方案,适配 API23 分层 HAR/HSP 大型工程架构。
更多推荐
所有评论(0)