摘要

ArkUI 提供 AppStorage 内存全局状态、PersistentStorage 持久绑定存储两套全局状态方案,原生零散使用存在问题:全局 key 分散、无统一类型约束、状态变更监听冗余、持久化绑定重复编写、登录 / 深色模式 / 用户信息多状态混杂。本文封装统一全局状态管理类 GlobalStore,集中管理全部全局变量、提供类型化读写、统一持久化绑定、批量监听状态变化、封装登录退出清空逻辑,联动前文 Preferences 持久存储工具。API23 重构 AppStorage 响应式监听、持久化同步机制、页面销毁自动解绑监听,修复多页面同时监听触发多次回调、持久化同步延迟、页面销毁监听残留内存泄漏等问题。

关键词

OpenHarmony;ArkTS;API23;AppStorage;PersistentStorage;全局状态;状态管理;持久化状态

一、引言

1.1 原生全局状态开发痛点

  1. 全局状态 Key 字符串硬编码分散在各个页面,修改易漏改报错;
  2. 无 TS 类型约束,存取全局变量全为 any,无代码提示易传错值;
  3. 深色模式、登录状态、用户信息等需要持久化,每个页面重复写 PersistentStorage 绑定;
  4. 多页面同时监听同一状态变更,多次重复执行回调;
  5. 退出登录时需要逐个清空所有全局变量,代码繁琐;
  6. 页面销毁未移除状态监听,后台持续触发回调造成页面报错。

1.2 两套核心 API 区分

  1. AppStorage:内存全局状态,整个应用单例共享,重启 APP 丢失;页面实时响应 @State 同等响应式更新 UI。
  2. PersistentStorage:桥接 AppStorage + Preferences,AppStorage 变量变更自动写入本地持久化,启动自动读取缓存回填。

API23 状态管理核心升级:

  1. PersistentStorage 同步写入优化,状态修改立刻落盘,不会重启丢失;
  2. watch 监听支持页面生命周期自动解绑,规避内存泄漏;
  3. 全局状态读写增加类型校验,非法赋值抛出日志警告;
  4. 多页面同变量监听去重,避免同一状态多次触发回调;
  5. 区分临时内存状态与持久化状态,分开管理互不干扰。

二、全局状态常量与类型定义 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 管理规范

  1. 所有全局变量统一存入GLOBAL_KEY常量,禁止页面手写字符串 key;
  2. 区分持久化 / 临时内存状态分组,便于维护;
  3. 新增全局状态必须同步更新GlobalStateType类型定义,保证类型安全。

6.2 持久化绑定规范

  1. PersistentStorage.link 仅在 EntryAbility.onCreate 全局执行一次;
  2. 仅用户配置、登录凭证等需要重启保留的变量开启持久化;
  3. 页面临时页面索引、弹窗临时标记等仅放内存,不持久化。

6.3 监听生命周期规范

  1. 页面使用 watch 监听必须保存解绑函数;
  2. aboutToDisappear 执行解绑,调用 clearPageWatch 清除全部监听;
  3. 高频页面避免过多 watch 监听,减少回调执行开销。

6.4 登录状态规范

  1. 登录统一调用 setLogin 批量赋值 token、用户信息、登录标记;
  2. 退出固定调用 logoutClear 一键清空全部登录相关状态;
  3. 路由、弹窗、接口请求全部读取 GlobalStore 判断登录,统一数据源。

6.5 UI 渲染规范

  1. 页面 @State 变量从 GlobalStore 读取初始值,搭配 watch 同步更新;
  2. 禁止页面直接操作 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 大型工程架构。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐