封装LocalStorage并支持过期时间

在浏览器开发中,我们经常需要在客户端存储数据,以便在不同页面或会话之间进行持久化。cookielocalStorage是常用的客户端存储机制。然而,它们在一些方面存在一些限制和差异。

  • cookie是存储在客户端的小型文本文件,每当用户与服务器进行通信时都会在请求和响应中传递。
  • cookie可以设置过期时间,可以是会话级别(浏览器关闭后过期)或持久性(在指定时间之后过期)。
  • cookie可以在不同页面之间共享,并且可以用于实现用户身份验证、记住用户偏好等功能。
  • 但是,每个请求都会将cookie发送到服务器,这可能会增加网络流量,并且对于每个请求都需要服务器进行处理。

localStorage

  • localStorage是HTML5中提供的一个客户端存储机制,用于存储较大量级的数据。
  • localStorage使用key-value的方式存储数据,并且数据存储在浏览器的本地。
  • localStorage没有过期时间的设置机制,存储的数据会一直存在于浏览器中,直到被手动删除。
  • localStorage的数据可以在不同页面之间共享,并且在浏览器关闭后仍然保留。
  • localStorage提供了简单的API来存储、获取和删除数据。

思考

在使用cookie时,我们可以设置有效期限,但localStorage本身并不具备该机制,它只能手动删除数据,否则数据将一直保存在浏览器中。那么,我们能否像cookie一样为localStorage设置有效期限呢?为了解决这个问题,我们可以对localStorage进行二次封装,以实现这一功能。

实现思路

在存储数据时,我们可以设置一个过期时间,并对存储的数据进行格式化,以便进行统一校验。在读取数据时,我们可以获取当前时间,并判断数据是否过期。如果数据过期了,我们可以将其删除。

代码实现

目录结构

image.png

enum.ts:定义枚举

// 字典 Dictionaries
// expire过期时间key
// permanent永久不过期
export enum Dictionaries {
  expire = '__expire__',
  permanent = 'permanent'
}
// 字典 Dictionaries
// expire过期时间key
// permanent永久不过期
export enum Dictionaries {
  expire = '__expire__',
  permanent = 'permanent'
}

type.ts:定义类型

import { Dictionaries } from "../enum";

export type Key = string; // key类型
export type Expire = Dictionaries.permanent | number; // 有效期类型

export interface Data<T> {  // 格式化data类型
  value: T;
  [Dictionaries.expire]: Expire;
}

export interface Result<T> { // 返回值类型
  message: string;
  value: T | null;
}

export interface StorageCls { // class方法约束
  set: <T>(key: Key, value: T, expire: Expire) => void;
  get: <T>(key: Key) => Result<T | null>;
  remove: (key: Key) => void;
  clear: () => void;
}
import { Dictionaries } from "../enum";

export type Key = string; // key类型
export type Expire = Dictionaries.permanent | number; // 有效期类型

export interface Data<T> {  // 格式化data类型
  value: T;
  [Dictionaries.expire]: Expire;
}

export interface Result<T> { // 返回值类型
  message: string;
  value: T | null;
}

export interface StorageCls { // class方法约束
  set: <T>(key: Key, value: T, expire: Expire) => void;
  get: <T>(key: Key) => Result<T | null>;
  remove: (key: Key) => void;
  clear: () => void;
}

index.ts:主要逻辑实现

import { StorageCls, Key, Expire, Data, Result } from "./type";
import { Dictionaries } from "./enum";

export class Storage implements StorageCls {
  // 存储接受 key value 和过期时间 默认永久
  public set<T = any>(key: Key, value: T, expire: Expire = Dictionaries.permanent) {
    // 格式化数据
    const data: Data<T> = {
      value,
      [Dictionaries.expire]: expire
    };
    // 存入localStorage
    localStorage.setItem(key, JSON.stringify(data));
  }

  public get<T = any>(key: Key): Result<T | null> {
    const value = localStorage.getItem(key);

    // 检查读取的数据是否有效
    if (value) {
      const obj: Data<T> = JSON.parse(value);
      const now = new Date().getTime();

      // 检查是否过期,如果过期则删除并返回提示信息
      if (typeof obj[Dictionaries.expire] === 'number' && obj[Dictionaries.expire] < now) {
        this.remove(key);
        return {
          message: `您的${key}已过期`,
          value: null
        };
      } else {
        // 数据有效,返回成功读取的值
        return {
          message: "成功

",
          value: obj.value
        };
      }
    } else {
      // 无效的key值
      console.warn('无效的key值');
      return {
        message: `key值无效`,
        value: null
      };
    }
  }

  // 删除某一项
  public remove(key: Key) {
    localStorage.removeItem(key);
  }

  // 清空所有值
  public clear() {
    localStorage.clear();
  }
}
import { StorageCls, Key, Expire, Data, Result } from "./type";
import { Dictionaries } from "./enum";

export class Storage implements StorageCls {
  // 存储接受 key value 和过期时间 默认永久
  public set<T = any>(key: Key, value: T, expire: Expire = Dictionaries.permanent) {
    // 格式化数据
    const data: Data<T> = {
      value,
      [Dictionaries.expire]: expire
    };
    // 存入localStorage
    localStorage.setItem(key, JSON.stringify(data));
  }

  public get<T = any>(key: Key): Result<T | null> {
    const value = localStorage.getItem(key);

    // 检查读取的数据是否有效
    if (value) {
      const obj: Data<T> = JSON.parse(value);
      const now = new Date().getTime();

      // 检查是否过期,如果过期则删除并返回提示信息
      if (typeof obj[Dictionaries.expire] === 'number' && obj[Dictionaries.expire] < now) {
        this.remove(key);
        return {
          message: `您的${key}已过期`,
          value: null
        };
      } else {
        // 数据有效,返回成功读取的值
        return {
          message: "成功

",
          value: obj.value
        };
      }
    } else {
      // 无效的key值
      console.warn('无效的key值');
      return {
        message: `key值无效`,
        value: null
      };
    }
  }

  // 删除某一项
  public remove(key: Key) {
    localStorage.removeItem(key);
  }

  // 清空所有值
  public clear() {
    localStorage.clear();
  }
}

rollup.js:简易打包配置(依赖:rolluprollup-plugin-typescript2typescript

旧版写法:

import ts from 'rollup-plugin-typescript2';
import path from 'path';

export default {
  input: './src/index.ts',
  output: {
    file: path.resolve(__dirname, './dist/index.js')
  },
  plugins: [
    ts()
  ]
};
import ts from 'rollup-plugin-typescript2';
import path from 'path';

export default {
  input: './src/index.ts',
  output: {
    file: path.resolve(__dirname, './dist/index.js')
  },
  plugins: [
    ts()
  ]
};

新版写法:

import ts from 'rollup-plugin-typescript2';
import path from 'path';
import { fileURLToPath } from 'url';

const metaUrl = fileURLToPath(import.meta.url);
const dirName = path.dirname(metaUrl);

export default {
  input: './src/index.ts',
  output: {
    file: path.resolve(dirName, './dist/index.js')
  },
  plugins: [
    ts()
  ]
};
import ts from 'rollup-plugin-typescript2';
import path from 'path';
import { fileURLToPath } from 'url';

const metaUrl = fileURLToPath(import.meta.url);
const dirName = path.dirname(metaUrl);

export default {
  input: './src/index.ts',
  output: {
    file: path.resolve(dirName, './dist/index.js')
  },
  plugins: [
    ts()
  ]
};

代码测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { Storage } from './dist/index.js';

    const sl = new Storage();

    // 五秒后过期
    sl.set('a', 123, new Date().getTime() + 5000);

    setInterval(() => {
      const a = sl.get('a');
      console.log(a);
    }, 500);
  </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script type="module">
    import { Storage } from './dist/index.js';

    const sl = new Storage();

    // 五秒后过期
    sl.set('a', 123, new Date().getTime() + 5000);

    setInterval(() => {
      const a = sl.get('a');
      console.log(a);
    }, 500);
  </script>
</body>
</html>

通过设置定时器,在五秒后观察值是否过期并删除,测试成功。 image.png