在 Vue 3 项目开发中,localStorage 是最常用的持久化手段。但在实际业务(如处理复杂表单、游戏参数、Proxy 对象)中,原生的 API 或一些全自动的第三方库往往会带来意想不到的坑。
本文记录了我们将 localStorage 封装从“智能猜测”优化为“显式指令”的思考过程。
1. 痛点:为什么不直接用 JSON.stringify?
很多封装方案(如 store.js 或 good-storage)为了省心,默认会对所有数据执行 JSON.stringify。这会带来两个核心问题:
-
字符串“引号癖”:如果你存入一个字符串
"hello",在本地存储里它会变成""hello""。这对其他系统读取或手动调试非常不友好。 -
内容歧义:如果用户在输入框输入了
{"a":1},当你尝试读取它时,工具可能“自作聪明”地把它解析成了一个对象,导致后续逻辑(如.trim())直接崩溃。
2. 设计哲学:明确比暗示更好
经过多次迭代,我们确定了这套封装的核心原则:
-
对象模式手动开启:处理
Array、Object或 Vue 3 的Proxy时,明确通过选项开启 JSON 模式。 - 字符串原样存取:默认不进行任何加工,保证数据的纯净和对旧数据的兼容。
-
混合参数设计:保留
key和defaultValue的位置参数,将配置项放入可选对象。
3. 最终代码实现
我们将配置项命名为 useJson,默认设为 false。
/**
* 统一本地存储工具 - 混合指令模式
*/
export const local = {
/**
* 存储数据
* @param {string} key 键名
* @param {any} value 值
* @param {Object} [options] 配置项
* @param {boolean} [options.useJson=false]
* - false (默认): 原样存储。
* - true: 序列化存储。处理对象脱壳及字符串转义。
*/
set(key, value, { useJson = false } = {}) {
const result = useJson ? JSON.stringify(value) : value;
localStorage.setItem(key, result);
},
/**
* 获取数据
* @param {string} key 键名
* @param {any} [defaultValue=null] 默认值
* @param {Object} [options] 配置项
* @param {boolean} [options.useJson=false]
* - false (默认): 直接返回原始字符串。
* - true: 执行 JSON.parse 解析。
*/
get(key, defaultValue = null, { useJson = false } = {}) {
const item = localStorage.getItem(key);
if (item === null) return defaultValue;
if (useJson) {
try {
return JSON.parse(item);
} catch (e) {
console.error(`Storage Parse Error for key "${key}":`, e);
return defaultValue;
}
}
// 原样返回原始字符串
return item;
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
}
};
4. 典型应用场景
场景 A:处理表单字符串 (Raw Mode)
处理 URL 参数或文本域内容时,内容可能包含特殊符号,我们不希望它被 JSON 干扰。
// 存储:直接存入明文,不带引号
local.set('game_params', textarea.value);
// 读取:直接拿回原始字符串
const text = local.get('game_params', '');
场景 B:处理 Vue 3 响应式对象 (JSON Mode)
利用 JSON.stringify 自动处理 Proxy 脱壳,实现响应式数据的深拷贝持久化。
// 存储:必须显式开启 useJson
local.set('custom_games', games.value, { useJson: true });
// 读取:解析回对象
const list = local.get('custom_games', [], { useJson: true });
5. 对比与总结
| 特性 | 原生 API | 传统库 (Store.js) | 本方案 (混合指令) |
|---|---|---|---|
| Proxy 处理 | 报错 | 自动序列化 | 指令序列化 |
| 字符串格式 | 明文 | 强制带引号 | 可选(默认明文) |
| 安全性 | 需手动判断类型 | 靠 try-catch 猜测 |
靠指令确权(最安全) |
| 参数体验 | 原始 | 顺序参数 | 混合对象参数(语义佳) |
结语
在工程实践中,我们往往倾向于“自动化”,但过度自动化的代价就是“不可控”。通过将 useJson 显式化,我们不仅解决了一个 Bug,更在团队中建立了一套关于数据所有权的契约:由开发者决定数据该如何被搬运。
Top comments (0)