通过项目中的实际例子,介绍 LocalStorage 在 Angular 开发中的使用场景

发布时间 2023-11-27 23:23:59作者: JerryWang_汪子熙

笔者之前在掘金社区的技术文章,介绍过自己项目组中负责开发的一款基于 Angular 的电商 SDK:

本文笔者会分享自己项目过程中,在设计这款 SDK 时使用 Web 开发领域的 LocalStorage 技术来实现的一个功能。

用户访问 Storefront 的 homepage,可以在 Language 的下拉菜单里,选择语言:

比如下图是语言切换成中文的效果:

同样还支持货币单位的切换:

客户的需求是,更改的这些设置,在关闭浏览器之后,仍然能够生效。意思是下次重新打开浏览器,Storefront homepage 仍然按照之前的设置去加载。

为此,我们团队采取的实现方式是,利用 Web 开发领域中的 LocalStorage 技术,来持久化用户指定的 Language 和货币单位的更改。

Local Storage 技术概述

Local Storage 技术是一种在Web 浏览器中用于客户端数据存储的机制。它允许 Web 应用程序在用户的本地浏览器上存储键值对形式的数据,这样用户在关闭浏览器窗口或页面后,数据仍然可以保留。这为开发者提供了一种在客户端持久保存数据的简单方式,而不必依赖于服务器。

我们在 Chrome 开发者工具 Application 面板里,可以清晰观察到这些 Key Value 键值对:

Local Storage 是Web Storage API 的一部分,与 Session Storage 不同,Local Storage 的数据在关闭浏览器后仍然存在。它使用键值对的形式存储数据,其中键和值都是字符串。该技术基于域名,即同一域名下的所有页面共享相同的 Local Storage

使用方式也非常简单直观,使用其提供的 get 和 set API 即可。

在 JavaScript 中,通过 localStorage 对象来访问和操作 Local Storage。以下是一些基本的 localStorage 操作:

存储数据:

// 存储数据
localStorage.setItem('username', 'John');

// 存储数字
localStorage.setItem('userAge', 25);

读取数据:

// 读取数据
const username = localStorage.getItem('username');
const userAge = localStorage.getItem('userAge');

删除数据:

// 删除数据
localStorage.removeItem('username');

清空所有数据:

// 清空所有数据
localStorage.clear();

我们项目组当时选择用 LocalStorage 技术来实现用户需求,也是基于了如下考虑:

  1. 持久性: 数据在浏览器关闭后仍然存在,适用于长期保存用户偏好设置等信息,比如我们项目中客户对于 Language 和货币单位的持久化需求。

  2. 容量: Local Storage 允许存储较大数量的数据(通常至少5MB),相对于 Cookie 的4KB而言,具备更大的容量,我们用来存储两个字符串值,可以说是绰绰有余。

  3. 简易性: 使用简单的键值对操作,易于理解和实现。

尽管 LocalStorage 数据存储在客户端,但是我们存储的仅仅是 Language 和货币单位,所以不存在敏感信息。

Local Storage 技术的项目实战

首先我们在 state-config.ts 文件里定义了 StorageSyncType 的枚举值:

可以看到目前我们打算支持 LocalStorage 和 SessionStorage 两种方式,方便 SDK 的 consumer 自己切换。

而 getStorage 函数用来封装获取 LocalStorage API 的行为:

export function getStorage(
  storageType: StorageSyncType,
  winRef: WindowRef
): Storage | undefined {
  let storage: Storage | undefined;

  switch (storageType) {
    case StorageSyncType.LOCAL_STORAGE: {
      storage = winRef.localStorage;
      break;
    }
    case StorageSyncType.SESSION_STORAGE: {
      storage = winRef.sessionStorage;
      break;
    }
    case StorageSyncType.NO_STORAGE: {
      storage = undefined;
      break;
    }

    default: {
      storage = winRef.sessionStorage;
    }
  }

这里 storage API 从依赖注入参数的 winReflocalStorage 或者 sessionStorage 字段里读取。
这两个字段都是全局对象 window 里的标准属性之一。

本文之前介绍的 LocalStorage API 就位于 localStorage 字段的原型链上,如下图高亮区域所示:

language 字段的 LocalStorage 读取

用户重新打开浏览器时,需要从 LocalStorage 里将存储的 language 字段值读取出来。

这个场景通过下图的 readFromStorage 函数实现:

export function readFromStorage(storage: Storage, key: string): unknown {
  if (isSsr(storage)) {
    return;
  }

  const storageValue = storage.getItem(key);
  if (!storageValue) {
    return;
  }

  return JSON.parse(storageValue);
}

逻辑很直接,首先判断是否是在 SSR 环境下,如果是就直接返回。因为按照笔者这篇文章的介绍,用户的个性化设置和私有数据页面,比如购物车,WishList 等,不应该被 Angular 服务器端渲染考虑,所以我们代码里如果 isSsr 检测函数返回 true,直接返回;否则调用 storage 的 API 即 getItem 返回浏览器 LocalStorage 里的存储值。

下面是调试器里的截图:

language 字段的 LocalStorage 的写入

当用户在 Storefront 界面上用下拉菜单更换 Language 字段值时,会调用 browser-storage.ts 里的 persistToStorage 方法:

我们可以看到这个方法里调用代码第 44 行的 setItem,将 Angular UI 上用户选定的值写入到 LocalStorage 里。

下拉菜单的实现位于 site-context-selector.component.html 文件内部。当用户切换下拉菜单值的时候,触发第 3 行 select 元素的 change 事件。

事件的处理函数主体就只有一个 active = $any($event).target.value 的赋值操作,这会触发定义在 active 属性上的 set 方法:

set 方法里调用 this.componentService.setActive(value, this.context),这个函数调用最终把执行投递到 storage.setItem(configKey, JSON.stringify(value)) 上去:

总结

在 Local Storage 出现之前,客户端数据存储主要依赖于 cookies。但是,cookies 存在许多不足,比如存储空间有限(通常只有 4KB),数据在每次 HTTP 请求时都会被发送到服务器,这会消耗更多的带宽。相比之下,Local Storage 提供了更大的存储空间(通常是 5MB),并且数据只存储在客户端,不会被发送到服务器。这使得 Local Storage 成为一种更有效的客户端数据存储方式。

本文介绍了笔者在实际 Angular 开发项目中使用 Local Storage 来持久化用户个性化选择的一个案例,希望能帮助到需要实现类似功能的开发者们。