DEV Community

Leo
Leo

Posted on

localStorage 封装:从“自动推断”到“确权控制”

在 Vue 3 项目开发中,localStorage 是最常用的持久化手段。但在实际业务(如处理复杂表单、游戏参数、Proxy 对象)中,原生的 API 或一些全自动的第三方库往往会带来意想不到的坑。

本文记录了我们将 localStorage 封装从“智能猜测”优化为“显式指令”的思考过程。


1. 痛点:为什么不直接用 JSON.stringify

很多封装方案(如 store.jsgood-storage)为了省心,默认会对所有数据执行 JSON.stringify。这会带来两个核心问题:

  1. 字符串“引号癖”:如果你存入一个字符串 "hello",在本地存储里它会变成 ""hello""。这对其他系统读取或手动调试非常不友好。
  2. 内容歧义:如果用户在输入框输入了 {"a":1},当你尝试读取它时,工具可能“自作聪明”地把它解析成了一个对象,导致后续逻辑(如 .trim())直接崩溃。

2. 设计哲学:明确比暗示更好

经过多次迭代,我们确定了这套封装的核心原则:

  • 对象模式手动开启:处理 ArrayObject 或 Vue 3 的 Proxy 时,明确通过选项开启 JSON 模式。
  • 字符串原样存取:默认不进行任何加工,保证数据的纯净和对旧数据的兼容。
  • 混合参数设计:保留 keydefaultValue 的位置参数,将配置项放入可选对象。

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();
    }
};

Enter fullscreen mode Exit fullscreen mode

4. 典型应用场景

场景 A:处理表单字符串 (Raw Mode)

处理 URL 参数或文本域内容时,内容可能包含特殊符号,我们不希望它被 JSON 干扰。

// 存储:直接存入明文,不带引号
local.set('game_params', textarea.value); 

// 读取:直接拿回原始字符串
const text = local.get('game_params', '');

Enter fullscreen mode Exit fullscreen mode

场景 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 });

Enter fullscreen mode Exit fullscreen mode

5. 对比与总结

特性 原生 API 传统库 (Store.js) 本方案 (混合指令)
Proxy 处理 报错 自动序列化 指令序列化
字符串格式 明文 强制带引号 可选(默认明文)
安全性 需手动判断类型 try-catch 猜测 靠指令确权(最安全)
参数体验 原始 顺序参数 混合对象参数(语义佳)

结语

在工程实践中,我们往往倾向于“自动化”,但过度自动化的代价就是“不可控”。通过将 useJson 显式化,我们不仅解决了一个 Bug,更在团队中建立了一套关于数据所有权的契约:由开发者决定数据该如何被搬运。

Top comments (0)