Harmony之路:全局状态管家——AppStorage与应用级数据管理

一、引入:为什么需要全局状态管理?

在复杂的应用开发中,我们经常需要在多个组件、甚至多个页面之间共享数据。如果使用@Prop或@Link层层传递,会导致代码耦合度高、维护困难。HarmonyOS提供了AppStorage作为应用级的全局状态管理方案,它就像一个"全局数据仓库",任何组件都可以直接访问和修改其中的数据,无需通过组件树层层传递。

二、讲解:AppStorage的核心用法

1. AppStorage基础操作

AppStorage提供了类似键值对的存储机制,支持多种数据类型的存储和读取。

基础数据操作示例:

import AppStorage from '@ohos.app.storage.AppStorage';

// 设置数据
AppStorage.SetOrCreate<string>('username', '张三');
AppStorage.SetOrCreate<number>('userAge', 25);
AppStorage.SetOrCreate<boolean>('isLogin', false);

// 获取数据
const username = AppStorage.Get<string>('username');
const userAge = AppStorage.Get<number>('userAge');
const isLogin = AppStorage.Get<boolean>('isLogin');
console.log('用户信息:', username, userAge, isLogin);

// 检查是否存在
const hasUsername = AppStorage.Has('username');
console.log('是否存在username:', hasUsername);

// 删除数据
AppStorage.Delete('username');

// 清空所有数据
AppStorage.Clear();

2. @StorageProp装饰器:单向绑定

@StorageProp装饰的变量会与AppStorage中的键建立单向绑定关系,当AppStorage中的数据变化时,组件会自动更新,但组件修改不会同步回AppStorage。

@StorageProp使用示例:

@Entry
@Component
struct UserProfile {
  @StorageProp('username') username: string = '';  // 单向绑定到AppStorage
  @StorageProp('userAge') userAge: number = 0;

  build() {
    Column({ space: 20 }) {
      Text(`用户名: ${this.username}`)
        .fontSize(20)
      
      Text(`年龄: ${this.userAge}`)
        .fontSize(18)
      
      Button('修改用户名')
        .onClick(() => {
          this.username = '李四';  // 修改不会同步到AppStorage
          console.log('组件内username:', this.username);
        })
      
      Button('同步到AppStorage')
        .onClick(() => {
          AppStorage.SetOrCreate('username', '王五');
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3. @StorageLink装饰器:双向绑定

@StorageLink装饰的变量会与AppStorage中的键建立双向绑定关系,组件修改会同步到AppStorage,AppStorage修改也会同步到组件。

@StorageLink使用示例:

@Entry
@Component
struct LoginPage {
  @StorageLink('isLogin') isLogin: boolean = false;  // 双向绑定到AppStorage
  @StorageLink('username') username: string = '';

  build() {
    Column({ space: 20 }) {
      Text(this.isLogin ? `欢迎回来,${this.username}` : '请先登录')
        .fontSize(20)
      
      Button(this.isLogin ? '退出登录' : '登录')
        .onClick(() => {
          this.isLogin = !this.isLogin;
          if (this.isLogin) {
            this.username = '张三';
          } else {
            this.username = '';
          }
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4. @StorageProp与@StorageLink的区别

特性 @StorageProp @StorageLink
数据流向 单向(AppStorage → 组件) 双向(AppStorage ↔ 组件)
组件修改 可以修改,不影响AppStorage 修改会同步到AppStorage
初始化 必须提供默认值 不需要默认值
使用场景 只读数据展示 需要双向同步的数据

5. 复杂数据类型管理

AppStorage支持存储对象和数组等复杂数据类型,但需要注意引用传递的特性。

对象类型存储示例:

class User {
  name: string;
  age: number;
  email: string;

  constructor(name: string, age: number, email: string) {
    this.name = name;
    this.age = age;
    this.email = email;
  }
}

// 存储对象
const user = new User('张三', 25, 'zhangsan@example.com');
AppStorage.SetOrCreate<User>('currentUser', user);

// 在组件中使用
@Component
struct UserCard {
  @StorageLink('currentUser') user: User = new User('', 0, '');

  build() {
    Column({ space: 10 }) {
      Text(`姓名: ${this.user.name}`)
      Text(`年龄: ${this.user.age}`)
      Text(`邮箱: ${this.user.email}`)
      
      Button('修改用户信息')
        .onClick(() => {
          this.user.age += 1;  // 修改会同步到AppStorage
        })
    }
    .padding(15)
    .border({ width: 1, color: Color.Blue })
  }
}

6. 数组类型存储

数组的存储和操作需要特别注意不可变更新的原则。

数组存储示例:

// 初始化数组
AppStorage.SetOrCreate<string[]>('todoList', ['学习ArkTS', '掌握AppStorage', '完成项目']);

@Component
struct TodoList {
  @StorageLink('todoList') todos: string[] = [];

  build() {
    Column({ space: 10 }) {
      ForEach(this.todos, (item: string, index: number) => {
        Row({ space: 10 }) {
          Text(`${index + 1}. ${item}`)
            .layoutWeight(1)
          
          Button('删除')
            .onClick(() => {
              // 不可变更新:创建新数组
              this.todos = this.todos.filter((_, i) => i !== index);
            })
        }
      })
      
      Button('添加任务')
        .onClick(() => {
          this.todos = [...this.todos, `新任务${this.todos.length + 1}`];
        })
    }
    .padding(15)
    .border({ width: 1, color: Color.Green })
  }
}

7. 实际应用场景

场景1:用户登录状态管理

// 用户服务类
class UserService {
  static login(username: string, password: string): boolean {
    // 模拟登录逻辑
    const success = username === 'admin' && password === '123456';
    if (success) {
      AppStorage.SetOrCreate('isLogin', true);
      AppStorage.SetOrCreate('username', username);
      AppStorage.SetOrCreate('userToken', 'mock-token-' + Date.now());
    }
    return success;
  }

  static logout() {
    AppStorage.SetOrCreate('isLogin', false);
    AppStorage.SetOrCreate('username', '');
    AppStorage.SetOrCreate('userToken', '');
  }
}

// 登录组件
@Component
struct LoginForm {
  @State username: string = '';
  @State password: string = '';

  build() {
    Column({ space: 15 }) {
      TextInput({ text: this.username, placeholder: '用户名' })
        .onChange((value: string) => {
          this.username = value;
        })
      
      TextInput({ text: this.password, placeholder: '密码' })
        .type(InputType.Password)
        .onChange((value: string) => {
          this.password = value;
        })
      
      Button('登录')
        .onClick(() => {
          if (UserService.login(this.username, this.password)) {
            console.log('登录成功');
          } else {
            console.log('登录失败');
          }
        })
    }
    .padding(20)
  }
}

// 用户信息展示组件
@Component
struct UserInfo {
  @StorageProp('isLogin') isLogin: boolean = false;
  @StorageProp('username') username: string = '';

  build() {
    Column() {
      if (this.isLogin) {
        Text(`欢迎,${this.username}`)
        Button('退出登录')
          .onClick(() => {
            UserService.logout();
          })
      } else {
        Text('请先登录')
      }
    }
  }
}

场景2:主题切换功能

// 主题配置
interface ThemeConfig {
  primaryColor: string;
  backgroundColor: string;
  textColor: string;
}

// 主题服务
class ThemeService {
  static readonly LIGHT_THEME: ThemeConfig = {
    primaryColor: '#007AFF',
    backgroundColor: '#FFFFFF',
    textColor: '#000000'
  };

  static readonly DARK_THEME: ThemeConfig = {
    primaryColor: '#0A84FF',
    backgroundColor: '#1C1C1E',
    textColor: '#FFFFFF'
  };

  static setTheme(theme: ThemeConfig) {
    AppStorage.SetOrCreate('theme', theme);
  }

  static toggleTheme() {
    const currentTheme = AppStorage.Get<ThemeConfig>('theme');
    const newTheme = currentTheme === this.LIGHT_THEME ? this.DARK_THEME : this.LIGHT_THEME;
    this.setTheme(newTheme);
  }
}

// 初始化主题
AppStorage.SetOrCreate('theme', ThemeService.LIGHT_THEME);

// 主题组件
@Component
struct ThemeSwitcher {
  @StorageLink('theme') theme: ThemeConfig = ThemeService.LIGHT_THEME;

  build() {
    Column({ space: 20 }) {
      Text('主题切换')
        .fontColor(this.theme.textColor)
      
      Button(this.theme === ThemeService.LIGHT_THEME ? '切换深色' : '切换浅色')
        .backgroundColor(this.theme.primaryColor)
        .onClick(() => {
          ThemeService.toggleTheme();
        })
    }
    .backgroundColor(this.theme.backgroundColor)
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

8. 性能优化与最佳实践

1. 合理使用装饰器

  • 对于只读数据,使用@StorageProp减少不必要的更新
  • 对于需要双向同步的数据,使用@StorageLink
  • 避免在build方法中频繁读取AppStorage

2. 数据持久化

AppStorage的数据在应用重启后会丢失,如果需要持久化存储,应该结合Preferences或数据库使用。

3. 数据清理

在应用退出或用户登出时,及时清理不需要的全局数据:

function clearUserData() {
  AppStorage.Delete('userToken');
  AppStorage.Delete('username');
  AppStorage.Delete('isLogin');
}

4. 类型安全

为AppStorage的数据定义接口类型,避免类型错误:

interface AppState {
  isLogin: boolean;
  username: string;
  userToken: string;
  theme: ThemeConfig;
}

// 使用类型断言确保类型安全
const state = AppStorage.Get<AppState>('appState');

三、总结:全局状态管理的核心要点

✅ 核心知识点回顾

  1. AppStorage是全局仓库:提供应用级别的数据存储和共享能力
  2. @StorageProp单向绑定:数据从AppStorage流向组件,组件修改不影响AppStorage
  3. @StorageLink双向绑定:组件和AppStorage保持数据同步
  4. 支持复杂数据类型:可以存储对象、数组等复杂数据结构
  5. 数据生命周期:应用重启后数据会丢失,需要结合持久化方案

⚠️ 常见问题与解决方案

  1. 数据不更新:检查是否使用了正确的装饰器(@StorageProp vs @StorageLink)
  2. 类型错误:确保AppStorage存储的数据类型与装饰器声明的类型一致
  3. 性能问题:避免在build方法中频繁读取AppStorage,使用@StorageProp/@StorageLink自动更新
  4. 数据丢失:重要数据需要结合Preferences进行持久化存储

🎯 最佳实践建议

  1. 合理划分数据:将全局共享的数据放入AppStorage,局部数据使用@State或@Prop
  2. 封装业务逻辑:将AppStorage的操作封装到服务类中,提高代码可维护性
  3. 类型安全:为AppStorage的数据定义接口类型,避免运行时错误
  4. 性能监控:对于频繁更新的数据,注意性能影响,必要时进行优化

下一步预告:在第九篇中,我们将学习手势处理与动画基础,掌握如何为应用添加流畅的交互效果和动画效果,提升用户体验。

posted @ 2025-12-23 23:03  蓝莓Reimay  阅读(3)  评论(0)    收藏  举报