文章导读

  • 游客空间使用 g,登录用户使用 u_<uid>,收藏、关注、历史和浏览量都按 scope 隔离。
  • 项目没有接后端账号体系,但仍然通过稳定 uid 让同一昵称复用自己的本地空间。
  • 登录、退出登录后都会重新灌入当前空间的状态,保证个人中心、详情页和列表页同步刷新。

页面效果

这篇文章对应的用户感知非常明确:游客态和登录态的个人中心统计不能串。游客在首页收藏一篇文章后,如果再用昵称登录,看到的应该是登录用户自己的收藏、关注和浏览记录,而不是把游客数据直接带过来。

登录页面截图

从页面上看,这只是一组数字和一个登录入口;从实现上看,它要求持久化、AppStorage 和页面响应式保持同一份事实来源。只要这里处理不好,收藏页、球员关注和浏览历史都会变得不可信。

实战拆解

羽球联盟没有对接服务端账号体系,但它并没有因此忽略“不同用户数据要隔离”这件事。PrefStore 先定义一组需要按用户空间隔离的 key:favoritesfollowshistoryviewCountsJson。随后,所有读写都通过 storeKey() 拼接 scope 前缀,游客走 g,登录用户走 u_<uid>

uid 的生成也很实用。项目没有把昵称直接当成 key 使用,而是通过一个稳定哈希函数把昵称映射成 ASCII uid。这样同一个昵称多次登录会回到同一份本地空间,不同昵称之间又能自然隔离。它当然不能替代真正的账号认证,但对于本地演示、页面联调和教程讲解已经足够。

真正决定页面体验的,是 rehydrateScoped()。用户登录、退出登录或重启应用时,当前空间的收藏、关注、历史和浏览量都会重新写回 AppStorage。由于详情页、个人中心、球员页都通过 @StorageLink 订阅这些状态,所以切换空间后,数字和按钮状态能立即反映当前身份,而不是停留在旧数据上。

关键代码

private static stableUidFor(nickname: string): string {
  let h: number = 5381;
  for (let i = 0; i < nickname.length; i++) {
    h = (((h << 5) + h) + nickname.charCodeAt(i)) & 0x7fffffff;
  }
  return 'U_' + h.toString(36).toUpperCase();
}

private async rehydrateScoped(): Promise<void> {
  const favStr = await this.store!.get(this.storeKey('favorites'), '[]') as string;
  const histStr = await this.store!.get(this.storeKey('history'), '[]') as string;
  const followStr = await this.store!.get(this.storeKey('follows'), '[]') as string;
  const viewJson = await this.store!.get(this.storeKey('viewCountsJson'), '{}') as string;
  AppStorage.setOrCreate<string[]>('favorites', JSON.parse(favStr) as string[]);
  AppStorage.setOrCreate<string[]>('history', JSON.parse(histStr) as string[]);
  AppStorage.setOrCreate<string[]>('follows', JSON.parse(followStr) as string[]);
  AppStorage.setOrCreate<string>('viewCountsJson', viewJson);
}

async login(nickname: string, avatarUrl?: string): Promise<void> {
  const uid: string = PrefStore.stableUidFor(nickname.trim());
  AppStorage.setOrCreate<string>('loggedInUserId', uid);
  this.scope = `u_\${uid}`;
  await this.rehydrateScoped();
}

这段代码的关键点在于“空间切换之后立刻重建当前视图所需状态”。如果只是把昵称和登录态写进 Preferences,而不重新加载当前用户的收藏、关注和历史,界面看上去就像已经登录,但内容仍然是上一位用户的数据。

取舍分析

这个方案最直接的好处,是不需要引入服务端就能把“身份隔离”这件事做对。对于教程型项目来说,这比单纯演示一个登录表单更有价值,因为它把用户态真正落到了收藏、关注和历史这些可见能力上。

它的边界也必须说清楚。基于昵称生成 uid 只是为了本地空间隔离,不是安全认证。如果后续接入真实账号体系,uid 的来源、游客转正式用户的数据迁移、跨设备同步等问题都要重新设计。但在当前项目规模下,这个方案已经足够轻量而且稳定。

设计落点

  • 收藏、关注、历史和浏览量都应以当前用户空间为准,而不是放在全局 key 下共享。
  • 登录态是全局状态,用户资产是分空间状态,两者要明确区分。
  • 空间切换后必须重新写回 AppStorage,页面统计和按钮状态才会同步。

易踩坑

  • 不要把收藏、关注、历史全部写成全局 key,否则游客和登录用户会互相污染。
  • 不要把 @StorageLink 派生结果缓存成普通字段,切换空间后很容易出现界面不同步。
  • 本地 uid 只是演示方案,不要把它包装成真实账号安全设计。

验证方式

  • 游客先收藏一篇文章,再登录新昵称,确认收藏数切换到该昵称自己的空间。
  • 退出登录回到游客态,确认游客收藏和浏览记录仍然存在。
  • 用同一昵称再次登录,确认此前该昵称的收藏、关注和历史能被恢复。

参考资料

小结

第 11 篇真正解决的是“同一个设备上的不同身份能不能看见各自的数据”。只要空间前缀、状态回灌和页面响应式三件事配合起来,哪怕没有后端账号体系,这个本地身份闭环也已经足够可靠。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐