组件内状态管不好跨页面的数据
@State、@Prop、@Link 解决的是单个组件或父子组件之间的数据问题。但真实应用里,用户登录状态、全局配置、购物车这些数据要跨多个页面共享,甚至重启后还要保留。这就要用到应用级状态管理。
应用级状态分三个:LocalStorage(页面级)、AppStorage(全局)、PersistentStorage(持久化)。
LocalStorage:页面范围内的共享
LocalStorage 让一个页面(及其子组件树)共享状态,作用域限制在页面内:
typescript
// 创建 LocalStorage 实例
let storage = LocalStorage.GetShared(); // 获取共享实例
storage.setOrCreate('pageTitle', '购物车');
@Entry(storage)
@Component
struct CartPage {
build() {
Column() {
Text('购物车页面')
}
}
}
@Entry(storage) 把 LocalStorage 绑定到页面入口,页面及其子树都能访问。适合"页面级的数据,离开页面就销毁"的场景。
AppStorage:全局共享
AppStorage 是进程级的全局状态,所有页面和组件都能读写。这是最常用的跨页面共享方案:
typescript
// 在任意地方设置全局状态
AppStorage.SetOrCreate('userId', '10086');
AppStorage.SetOrCreate('isLoggedIn', true);
@Entry
@Component
struct HomePage {
@StorageLink('userId') userId: string = ''; // 双向绑定到 AppStorage
@StorageProp('isLoggedIn') isLoggedIn: boolean = false; // 单向读取
build() {
Column({ space: 16 }) {
Text(`当前用户:${this.userId}`)
Text(`登录状态:${this.isLoggedIn}`)
Button('切换用户')
.onClick(() => {
this.userId = '10087'; // 修改,全局同步
})
}
}
}
两种访问方式:
@StorageLink:双向绑定,改本地变量会同步到 AppStorage,其他页面也更新@StorageProp:单向读取,只读全局值,本地修改不影响全局
@StorageLink 与 @StorageProp 的流向
AppStorage['userId'] = '10086'
│
├── @StorageLink userId ──> 组件A(可读写,改了同步回全局)
│ 组件A 改 userId='10087'
│ │
│ └──> AppStorage 更新
│ └──> 组件B 的 userId 也变
│
└── @StorageProp userId ──> 组件C(只读,改了不影响全局)
PersistentStorage:持久化,重启不丢
AppStorage 是内存态,应用重启就清空。PersistentStorage 把它备份到磁盘,重启后恢复:
typescript
// 把 AppStorage 的 userId 持久化到磁盘
PersistentStorage.PersistProp('userId', '10086');
// 或持久化 isLoggedIn
PersistentStorage.PersistProp('isLoggedIn', true);
PersistProp(key, value) 的作用是:把 AppStorage 中的 key 关联到持久化存储,设置初始值。之后:
- 修改 AppStorage 里的
userId,会自动持久化 - 应用重启,
userId从磁盘恢复
典型用法------登录状态持久化:
typescript
// 应用启动时(如 EntryAbility 的 onCreate)
onCreate() {
PersistentStorage.PersistProp('loginToken', ''); // 初始为空
}
// 登录成功后
function onLoginSuccess(token: string) {
AppStorage.SetOrCreate('loginToken', token); // 自动持久化
}
// 任何页面读取
@StorageLink('loginToken') loginToken: string = '';
if (this.loginToken) {
// 已登录
} else {
// 未登录
}
三者关系图
PersistentStorage(磁盘,持久化)
│ PersistProp 关联
↓
AppStorage(内存,全局共享)
│ SetOrCreate / @StorageLink / @StorageProp
↓
组件(各页面读写)
LocalStorage(页面级,独立于上面两条线)
│ @Entry(storage)
↓
页面及子树组件
关键理解:PersistentStorage 不是独立存储,它是 AppStorage 的持久化备份。数据同时存在内存(AppStorage)和磁盘(PersistentStorage),内存态读写快,磁盘态保证重启不丢。
完整案例:购物车跨页面共享
购物车数据多个页面都要用,是典型的应用级状态场景:
typescript
// 商品对象
@Observed
class CartItem {
id: number;
name: string;
price: number;
quantity: number;
constructor(id: number, name: string, price: number, quantity: number) {
this.id = id;
this.name = name;
this.price = price;
this.quantity = quantity;
}
}
// 应用启动时初始化购物车
PersistentStorage.PersistProp('cartItems', []);
// 商品列表页:加入购物车
@Entry
@Component
struct GoodsPage {
@StorageLink('cartItems') cartItems: CartItem[] = [];
build() {
List() {
ForEach([1, 2, 3], (id: number) => {
ListItem() {
Row() {
Text(`商品 ${id}`)
Blank()
Button('加入购物车')
.onClick(() => {
this.cartItems.push(new CartItem(id, `商品${id}`, 9.9, 1));
// @StorageLink 修改,全局同步 + 自动持久化
})
}
}
}, (id: number) => id.toString())
}
}
}
// 购物车页:展示并修改
@Entry
@Component
struct CartPage {
@StorageLink('cartItems') cartItems: CartItem[] = [];
build() {
Column() {
Text(`共 ${this.cartItems.length} 件商品`)
List() {
ForEach(this.cartItems, (item: CartItem) => {
ListItem() {
Row() {
Text(item.name)
Text(`¥${item.price}`)
Text(`x${item.quantity}`)
}
}
}, (item: CartItem) => item.id.toString())
}
}
}
}
商品页加购物车 → cartItems 更新 → 购物车页自动刷新 → 同时持久化到磁盘。这个链路覆盖了应用级状态的完整用法。
@Provide/@Consume:跨层级共享的另一选择
前面讲应用级状态时提过 @Provide/@Consume,它也是共享机制,但作用域是组件树而非全进程:
typescript
// 祖先组件提供
@Component
struct Ancestor {
@Provide('theme') theme: string = 'light'; // 提供共享数据
build() {
// 很深层的子组件
GrandChild()
}
}
// 深层子组件消费(不用中间逐层传)
@Component
struct GrandChild {
@Consume('theme') theme: string; // 消费共享数据
build() {
Text(`当前主题:${this.theme}`)
}
}
@Provide/@Consume 适合组件树内的跨层级共享(如主题、语言),AppStorage 适合全应用共享(如登录态)。前者随组件树销毁而回收,后者进程级常驻,按生命周期选。
小小总结一下
-
区分数据生命周期再选型 。页面内临时数据用
@State,组件树内共享用@Provide/@Consume,全局共享用AppStorage,要持久化再加PersistentStorage。别所有数据都往 AppStorage 里塞。 -
PersistProp的 key 要统一管理。持久化 key 散落各处容易冲突,建议集中定义常量。 -
同步引用时注意 type 。
AppStorage.SetOrCreate的类型要和@StorageLink声明一致,类型对不上会有隐式转换问题。 -
持久化数据只存可序列化的 。
PersistentStorage存的是可序列化数据,函数、回调、资源引用别往里放,恢复时会是空或异常。