前端技巧分享: 增强Web本地存储的数据类型支持

109 阅读3分钟

引言

在现代 Web 开发中,localStoragesessionStorage 是我们存储数据的好帮手,它们简单又实用。但它们只能存字符串,这在处理复杂数据时就显得不够用了。
以前,开发者处理 MapSetSymbolBigInt 这些不是字符串的数据类型,或者 NaNundefined 这样的特殊值时,都得用 JSON 来转换,这不仅让代码变得复杂,还容易出错。
为了解决这一痛点,本文将对 Web 本地存储的存取方法进行封装,实现自动的序列化与反序列化功能。通过这一封装,开发者能够直接存储和获取几乎所有的 JavaScript 数据类型,无需再进行繁琐的手动转换操作。这不仅极大地简化了代码结构,降低了出错的概率,还显著提升了开发效率,为前端开发带来了更便捷、高效的体验。

不同数据类型序列化&反序列化集合

下面列出了我们为不同数据类型设计的序列化和反序列化规则:
通过这样的设计,为每种数据类型定义了清晰的序列化和反序列化规则,确保在存储和读取数据时能够准确无误地处理各种复杂数据结构,使得本地存储能够像处理简单字符串数据一样轻松应对多样化的数据类型。

interface DataTypeOperation {
    deserialize(value?: unknown): unknown;
    serialize(value: unknown): string;
}
const dataTypeOperations: Map<string, DataTypeOperation> = new Map([
    [
        "Map",
        {
            deserialize: (
                value: Iterable<readonly [unknown, unknown]> | null | undefined
            ) => new Map(value),
            serialize: (value: Iterable<unknown> | ArrayLike<unknown>) => {
                return JSON.stringify({ type: "Map", value: Array.from(value) });
            },
        },
    ],
    [
        "Set",
        {
            deserialize: (
                value: Iterable<readonly [unknown, unknown]> | null | undefined
            ) => new Set(value),
            serialize: (value: Iterable<unknown> | ArrayLike<unknown>) => {
                return JSON.stringify({ type: "Set", value: Array.from(value) });
            },
        },
    ],
    [
        "Symbol",
        {
            deserialize: (value: string) => Symbol.for(value),
            serialize: (value: symbol) => {
                return JSON.stringify({ type: "Symbol", value: Symbol.keyFor(value) });
            },
        },
    ],
    [
        "BigInt",
        {
            deserialize: (value: bigint) => BigInt(value),
            serialize: (value: string | number | bigint | boolean) => {
                return JSON.stringify({ type: "BigInt", value: value.toString() });
            },
        },
    ],
    [
        "NaN",
        {
            deserialize: () => NaN,
            serialize: (value: unknown) => {
                return JSON.stringify({ type: "NaN", value: value });
            },
        },
    ],
    [
        "Undefined",
        {
            deserialize: () => undefined,
            serialize: (value: unknown) => {
                return JSON.stringify({ type: "Undefined", value: value });
            },
        },
    ],
    [
        "Object",
        {
            deserialize: (value: Record<string, { type: string, value: any }>) => {
                const resultValue: Record<string, unknown> = {};
                value && Object.entries(value).forEach(([key, values]) => {
                    const { type, value } = values;
                    if (type !== "Object" && dataTypeOperations.has(type)) {
                        resultValue[key] = dataTypeOperations.get(type)!.deserialize(value);
                    } else {
                        resultValue[key] = JSON.parse(value);
                    }
                });
                return resultValue;
            },
            serialize: (value: any) => {
                const resultValue: Record<string, unknown> = {};
                value && Object.entries(value).forEach(([key, value]) => {
                    const type = Object.prototype.toString
                        .call(value)
                        .replace(/^[object (.+)]$/, "$1");
                    if (type !== "Object" && dataTypeOperations.has(type)) {
                        resultValue[key] = JSON.parse(dataTypeOperations.get(type)!.serialize(value));
                    } else {
                        resultValue[key] = {
                            type,
                            value: JSON.stringify(value),
                        };
                    }
                });
                return JSON.stringify({ type: "Object", value: resultValue });
            },
        },
    ],
]);

抽象类 WebStorage

为了简化本地存储的操作,我们设计了一个抽象类 WebStorage
提供了一系列通用的存储操作方法,包括获取、设置、删除、清空数据,以及获取存储的键和长度等。同时,它还提供了扩展机制,允许开发者通过 addTypeOperation 方法添加自定义数据类型的序列化和反序列化操作,进一步增强了其灵活性和可扩展性。

export abstract class WebStorage {
    private storage: Storage;
    constructor(storage: storage) {
        if(storage === STORAGE.LOCAL) {
            this.storage = localStorage;
        }else if (storage === STORAGE.SESSION) {
            this.storage = sessionStorage;
        }else{
            throw new Error('storage type error');
        }
    }
    addTypeOperation(type: string, operations: DataTypeOperation) {
        dataTypeOperations.set(type, operations);
    }
    get(key: string) {
        const data = this.storage.getItem(key);
        if (!data || !/^{.*}$/.test(data)) return data;

        const { type, value, ...remainingKeys } = JSON.parse(data);
        if (Object.keys(remainingKeys).length === 0 && type) {
            if (dataTypeOperations.has(type)) {
                return dataTypeOperations.get(type)!.deserialize(value);
            } else {
                return value;
            }
        } else {
            return JSON.parse(data);
        }
    }
    set(key: string, value: unknown) {
        let type = Object.prototype.toString
            .call(value)
            .replace(/^[object (.+)]$/, "$1");
        if (typeof value === "number") {
            type = isNaN(value) ? "NaN" : "Number";
        }
        if (dataTypeOperations.has(type)) {
            value = dataTypeOperations.get(type)!.serialize(value);
        } else {
            value = JSON.stringify({ type, value });
        }
        this.storage.setItem(key, value as string);
    }
    remove(key: string) {
        this.storage.removeItem(key);
    }
    clear() {
        this.storage.clear();
    }
    key(index: number) {
        return this.storage.key(index);
    }
    get length() {
        return this.storage.length;
    }
    get getItem() {
        return this.get;
    }
    get setItem() {
        return this.set;
    }
    get removeItem() {
        return this.remove;
    }
}

基于抽象类实现 localstore

我们用 WebStorage 这个抽象类来创建 LocalStore 类,专门用来操作本地存储:
过单例模式,我们确保在整个应用中只有一个 LocalStore 实例,方便开发者在不同的模块中统一地访问和操作本地存储,避免了不必要的实例化开销和数据不一致问题

class LocalStore extends WebStorage {
    static #instance: LocalStore;
    private constructor() {
        super(STORAGE.LOCAL);
    }
    static getInstance(){
        return LocalStore.#instance ??= new LocalStore();
    }
}

export const localStore = LocalStore.getInstance();

基于抽象类实现 sessionStore

同样,我们还创建了 SessionStore 类来处理会话存储:
SessionStore 类同样采用单例模式,为开发者提供了简洁而高效的会话存储操作接口,使得在处理会话相关数据时能够更加方便快捷,同时保持与本地存储操作的一致性和易用性。

class SessionStore extends WebStorage {
    static #instance: SessionStore;
    private constructor() {
        super(STORAGE.LOCAL);
    }
    static getInstance(){
        return SessionStore.#instance ??= new SessionStore();
    }
}

export const sessionStore = SessionStore.getInstance();

感谢阅读,敬请斧正!