一、前言:为什么需要逻辑层存取?

在 HarmonyOS NEXT 应用开发中,状态管理是一个核心话题。很多开发者熟悉在 UI 组件中使用 @LocalStorageProp 和 @LocalStorageLink 装饰器来实现数据与视图的联动,但当数据流转到逻辑层(ViewModel、Manager、Util 等非 UI 类)时,如何优雅地存取 LocalStorage 数据,却是一个容易被忽视但又至关重要的问题。

本文聚焦于逻辑处理存取这一特定场景,深入探讨:

  • LocalStorage 在逻辑类中的定位与边界

  • 核心 API(getSharedsetOrCreateget)的用法与陷阱

  • 如何通过 UIAbility 绑定实现跨页面共享

  • 与 UI 装饰器的区别与协作

  • 常见错误与最佳实践

定位说明:本文聚焦于“逻辑处理存取”,即脱离了 UI 绑定场景下的数据操作,主要面向 ViewModel、Manager 等非 UI 类。如果您的需求是在 UI 组件内实现数据同步更新,请参考 @LocalStorageProp 和 @LocalStorageLink 的官方文档。


二、LocalStorage 核心概念回顾

2.1 什么是 LocalStorage?

LocalStorage 是 HarmonyOS NEXT 提供的页面级 UI 状态存储机制,本质上是内存中的一个“键值对数据库”。

核心特征:

维度 说明
作用范围 页面级(单个 @Entry 组件树)或 UIAbility 级(通过共享实例)
存储位置 内存 
生命周期 由引用决定,最后一个引用释放时被 JS Engine 垃圾回收 
读写特性 同步操作,读写时会阻塞后续代码执行 

2.2 LocalStorage 在鸿蒙存储体系中的定位

理解 LocalStorage 在整个鸿蒙存储体系中的位置,有助于我们做出正确的技术选型:

存储方案 作用范围 存储位置 生命周期 适用场景
LocalStorage 页面级 / UIAbility 级 内存 页面销毁时清除 页面内或 Ability 内状态共享 
AppStorage 应用全局 内存 应用退出时清除 全局状态管理 
PersistentStorage 应用全局 + 持久化 磁盘 长期保留 关键配置持久化 
Preferences 应用级 磁盘 长期保留 轻量级数据存储(<16MB)

关键理解:LocalStorage 不是持久化方案,应用退出后数据即丢失。如果需要持久化,请使用 PersistentStorage 或 Preferences。


三、逻辑类中存取数据:核心 API 详解

当我们在逻辑类(非 UI 组件)中使用 LocalStorage 时,核心操作是存取数据,不涉及 UI 刷新和渲染。

3.1 获取 LocalStorage 实例:LocalStorage.getShared()

在逻辑类中,我们无法像 UI 组件那样通过 @Entry(storage) 注入实例,因此需要通过 getShared() 方法获取当前 UIAbility 中共享的 LocalStorage 实例。

typescript

// 获取当前 UIAbility 共享的 LocalStorage 实例
let storage = LocalStorage.getShared();

⚠️ 重要前提getShared() 方法仅能获取当前 Stage 通过 windowStage.loadContent() 传入的 LocalStorage 实例,否则返回 undefined

3.2 存储数据:setOrCreate()

推荐使用 setOrCreate() 方法进行数据存储:

typescript

storage.setOrCreate('pageID', '20260710120000');
storage.setOrCreate('userInfo', { name: 'John', age: 25 });

setOrCreate 的行为特点

  • 如果 propName 不存在,创建该属性并赋值

  • 如果 propName 已存在且新值与旧值不同,更新值

  • 注意:如果值未发生变化,状态变量不会触发 UI 刷新

3.3 读取数据:get()

读取数据时需要注意判空处理,因为指定的 key 可能不存在:

typescript

// 推荐写法:带默认值
let pageID: string = storage.get('pageID') ?? '';
let userAge: number = storage.get('userAge') ?? 0;

// 或者使用类型断言
let userName = storage.get('userName') as string | undefined;

3.4 完整示例:逻辑管理类

下面是一个典型的管理类实现,演示了在逻辑层存取数据的方式:

typescript

// TestMgr.ets
export default class TestMgr {
  private static instance: TestMgr | undefined = undefined;

  public static Ins(): TestMgr {
    if (!TestMgr.instance) {
      TestMgr.instance = new TestMgr();
    }
    return TestMgr.instance;
  }

  // 存储数据
  public setPageID(pageID: string): void {
    let storage = LocalStorage.getShared();
    storage.setOrCreate('pageID', pageID);
  }

  // 读取数据(带判空)
  public getPageID(): string {
    let storage = LocalStorage.getShared();
    return storage.get('pageID') ?? '';
  }

  // 存储复杂对象
  public setUserInfo(userInfo: Record<string, string | number>): void {
    let storage = LocalStorage.getShared();
    storage.setOrCreate('userInfo', userInfo);
  }

  // 读取复杂对象
  public getUserInfo(): Record<string, string | number> | null {
    let storage = LocalStorage.getShared();
    return storage.get('userInfo') ?? null;
  }
}

四、核心场景:让 LocalStorage 在 UIAbility 中共享

4.1 为什么需要在 UIAbility 中绑定?

如果不进行特殊处理,LocalStorage 实例的作用范围仅限于单个 @Entry 装饰的组件及其子组件树

为了实现以下目标,我们需要将 LocalStorage 实例绑定到 UIAbility:

  1. 跨页面共享:同一 UIAbility 内的多个页面访问同一份数据

  2. 逻辑类访问:非 UI 组件通过 getShared() 获取实例

4.2 在 UIAbility 中创建并绑定 LocalStorage

在 EntryAbility.ets 中创建 LocalStorage 实例,并通过 windowStage.loadContent() 的第二个参数传入:

typescript

// EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  // 在 Ability 层面创建 LocalStorage 实例
  storage: LocalStorage = new LocalStorage({
    'appTheme': 'light',
    'userToken': ''
  });

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 将 storage 实例传递给页面加载
    windowStage.loadContent('pages/Index', this.storage, (err) => {
      if (err.code) {
        console.error('Failed to load content', JSON.stringify(err));
        return;
      }
      console.info('Succeeded in loading content');
    });
  }
}

4.3 在逻辑类中获取共享实例

绑定完成后,在任何页面或逻辑类中都可以通过 getShared() 获取同一个实例:

typescript

// 任意页面或逻辑类中
let storage = LocalStorage.getShared();
let token = storage.get('userToken') ?? '';

4.4 多个页面共享数据的完整示例

页面 A(Index.ets) :

typescript

import { router } from '@kit.ArkUI';
import TestMgr from './TestMgr';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  aboutToAppear(): void {
    // 通过逻辑类存储数据
    TestMgr.Ins().setPageID('page_001');
    let id = TestMgr.Ins().getPageID();
    console.log('Page ID:', id);
  }

  build() {
    Column() {
      Text(this.message)
        .onClick(() => {
          router.pushUrl({ url: 'pages/Index2' });
        })
    }
  }
}

页面 B(Index2.ets) :

typescript

import TestMgr from './TestMgr';

@Entry
@Component
struct Index2 {
  @State pageID: string = '';

  aboutToAppear(): void {
    // 从逻辑类读取数据
    this.pageID = TestMgr.Ins().getPageID();
  }

  build() {
    Column() {
      Text('Page ID from LocalStorage: ' + this.pageID)
    }
  }
}

五、逻辑存取 vs UI 装饰器:区别与协作

5.1 两条并行的使用路径

LocalStorage 提供了两条并行的数据操作路径,理解它们的区别至关重要:

使用方式 适用场景 数据同步 UI 刷新
逻辑存取get/setOrCreate 逻辑类、ViewModel、Manager 不触发 UI 自动刷新 需要手动处理
UI 装饰器@LocalStorageProp/@LocalStorageLink UI 组件内部 自动同步 自动触发渲染 

5.2 装饰器快速回顾

@LocalStorageProp - 单向同步

  • 从 LocalStorage 同步数据到组件

  • 组件内修改不会写回 LocalStorage

  • LocalStorage 变化会覆盖组件本地值

typescript

@LocalStorageProp('userName') userName: string = 'Default';

@LocalStorageLink - 双向同步

  • 从 LocalStorage 同步数据到组件

  • 组件内修改会写回 LocalStorage

  • LocalStorage 变化会同步到所有绑定的组件

typescript

@LocalStorageLink('userName') userName: string = 'Default';

5.3 逻辑存取与 UI 装饰器的协作模式

在实践中,两种方式经常需要配合使用:

typescript

// 场景:登录成功后,在逻辑类中存储用户信息
export class LoginManager {
  static saveUserToken(token: string): void {
    let storage = LocalStorage.getShared();
    storage.setOrCreate('userToken', token);
    storage.setOrCreate('isLoggedIn', true);
  }
}

// 在 UI 组件中通过装饰器监听状态变化
@Entry
@Component
struct ProfilePage {
  @LocalStorageLink('isLoggedIn') isLoggedIn: boolean = false;
  @LocalStorageProp('userToken') userToken: string = '';

  build() {
    if (this.isLoggedIn) {
      Text('已登录,Token: ' + this.userToken)
    } else {
      Text('未登录')
    }
  }
}

六、常见问题与避坑指南

6.1 getShared() 返回 undefined

问题:在逻辑类中调用 LocalStorage.getShared() 返回 undefined

原因getShared() 只能获取通过 windowStage.loadContent() 传入的 LocalStorage 实例。如果没有在 UIAbility 中正确绑定,则返回 undefined。

解决方案:确保在 UIAbility 的 onWindowStageCreate 中正确传入实例。

6.2 属性类型不可变更

问题:创建 LocalStorage 后,尝试用不同类型的值更新属性。

原因:LocalStorage 创建后,命名属性的类型不可更改。

typescript

// ❌ 错误示例
let storage = new LocalStorage({ 'age': 18 }); // age 为 number
storage.setOrCreate('age', '18'); // 运行时错误:类型不匹配

// ✅ 正确示例
storage.setOrCreate('age', 20); // 使用相同类型

6.3 不支持 Function 类型

问题:尝试在 LocalStorage 中存储函数。

原因:LocalStorage 不支持装饰 Function 类型的变量,框架会抛出运行时错误。

6.4 频繁修改复杂对象影响性能

问题:频繁修改 LocalStorage 中的复杂对象导致应用卡顿。

原因:LocalStorage 的读写操作是同步的,会阻塞后续代码执行。

建议

  • 避免在循环或高频操作中频繁读写

  • 复杂对象可以考虑序列化后存储

  • 大对象建议使用 Preferences 或数据库

6.5 生命周期管理

问题:LocalStorage 数据意外丢失。

原因:LocalStorage 是内存存储,当最后一个引用被释放时会被 JS Engine 回收。

理解:LocalStorage 的生命周期由引用计数决定,不是简单的“页面销毁即清除”。只要还有引用存在,数据就保留。


七、最佳实践总结

7.1 何时使用逻辑存取

场景 推荐方式
ViewModel 中管理业务状态 逻辑存取(get/setOrCreate
Manager 单例中存储配置 逻辑存取
跨页面传递临时数据 逻辑存取 + UIAbility 绑定
UI 组件内需要自动刷新 @LocalStorageLink / @LocalStorageProp
兄弟组件状态同步 @LocalStorageLink 

7.2 架构建议

text

┌─────────────────────────────────────────────────────────┐
│                    UIAbility                           │
│  ┌─────────────────────────────────────────────────┐   │
│  │           LocalStorage 实例 (共享)              │   │
│  │  { pageID: '001', userToken: 'xxx', ... }      │   │
│  └─────────────────────────────────────────────────┘   │
│              ↑                    ↑                     │
│     ┌────────┴────────┐  ┌──────┴──────┐              │
│     │  逻辑层存取      │  │  UI层装饰器  │              │
│     │  (Manager/VM)   │  │ (@Link/Prop) │              │
│     │  get/setOrCreate│  │  自动刷新    │              │
│     └─────────────────┘  └─────────────┘              │
└─────────────────────────────────────────────────────────┘

7.3 关键要点

  1. getShared() 先决条件:必须在 UIAbility 中通过 loadContent 传入实例

  2. 判空是必须的storage.get('key') ?? defaultValue

  3. 类型一致性:属性类型创建后不可更改

  4. 同步操作特性:不推荐频繁修改复杂对象

  5. 装饰器 vs 逻辑存取:各司其职,按需选择


八、参考资料

  1. 腾讯云开发者社区 - 【HarmonyOS NEXT】深入理解LocalStorage 

  2. CSDN - 鸿蒙开发笔记-9-应用状态管理 

  3. 腾讯云开发者社区 - harmony OS NEXT-UIAbility内状态-LocalStorage详细介绍 

  4. CSDN - 鸿蒙HarmonyOS Next开发:LocalStorage 

  5. CSDN - 第六章HarmonyOS NEXT 鸿蒙储存 

  6. CSDN - ArkTS语言状态管理-LocalStorage 

  7. CSDN - 鸿蒙 数据存储 

  8. CSDN - HarmonyOS NEXT鸿蒙的各种状态管理和持久化存储详解 

  9. 博客园 - 鸿蒙Next下页面级存储LocalStorage用法全解析 

  10. 掘金 - HarmonyOS NEXT-应用状态管理 

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐