前端工具封装

前端工具封装

  1. Book
  2. in 6 hours
  3. 4 min read
type StorageType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'

interface StorageData<T = unknown> {
  type: StorageType
  data: T
}

const STORAGE_TYPE_MAP = {
  string: 'string',
  number: 'number',
  boolean: 'boolean',
  null: 'null',
  object: 'object',
  array: 'array'
} as const

// 类型守卫验证函数
function isValidStorageData(data: any): data is StorageData {
  return data && typeof data === 'object' && 'type' in data && 'data' in data
}

class TypedStorage {
  // 类型安全的存储方法
  static setItem<T>(key: string, value: T): void {
    let type: StorageType = STORAGE_TYPE_MAP.string
    
    if (value === null) {
      type = STORAGE_TYPE_MAP.null
    } else if (Array.isArray(value)) {
      type = STORAGE_TYPE_MAP.array
    } else if (typeof value === 'object') {
      type = STORAGE_TYPE_MAP.object
    } else if (typeof value === 'number') {
      type = STORAGE_TYPE_MAP.number
    } else if (typeof value === 'boolean') {
      type = STORAGE_TYPE_MAP.boolean
    }

    const storageData: StorageData = {
      type,
      data: value as Exclude<T, Function> // 过滤函数类型
    }

    localStorage.setItem(key, JSON.stringify(storageData))
  }

  // 类型安全的读取方法(带自动类型推断)
  static getItem<T>(key: string): T | null {
    const storedValue = localStorage.getItem(key)
    if (!storedValue) return null

    try {
      const parsedData = JSON.parse(storedValue)
      if (!isValidStorageData(parsedData)) {
        return storedValue as unknown as T // 兼容原生字符串
      }

      switch (parsedData.type) {
        case STORAGE_TYPE_MAP.number:
          return Number(parsedData.data) as T
        case STORAGE_TYPE_MAP.boolean:
          return Boolean(parsedData.data) as T
        case STORAGE_TYPE_MAP.null:
          return null as T
        case STORAGE_TYPE_MAP.object:
        case STORAGE_TYPE_MAP.array:
          return parsedData.data as T
        default:
          return parsedData.data as T
      }
    } catch {
      return storedValue as unknown as T // 解析失败返回原始字符串
    }
  }

  // 其他原生方法保持类型安全
  static removeItem(key: string): void {
    localStorage.removeItem(key)
  }

  static clear(): void {
    localStorage.clear()
  }
}

// 使用示例
interface User {
  name: string
  age: number
}

// 存储复杂对象
TypedStorage.setItem<User>('user', { name: 'Alice', age: 28 })

// 读取时自动推断类型
const user = TypedStorage.getItem<User>('user') // 类型为 User | null
console.log(user?.age.toFixed(2)) // 无需类型断言

// 存储基础类型
TypedStorage.setItem<number>('counter', 42)
const counter = TypedStorage.getItem<number>('counter') // number | null

// 存储数组
TypedStorage.setItem<number[]>('scores', [90, 85, 95])
const scores = TypedStorage.getItem<number[]>('scores') // number[] | null

主要特性说明:

  1. 类型自动推断
    • 通过泛型参数和类型守卫实现精确的类型推断
    • 读取时自动恢复原始数据类型(number/boolean/object等)
  2. 安全存储结构
    • 使用{ type: StorageType, data: T }结构存储类型信息
    • 自动处理JSON序列化/反序列化
  3. 异常处理
    • 兼容原生localStorage存储的普通字符串
    • 解析失败时降级返回原始字符串
  4. 类型过滤
    • 使用Exclude<T, Function>过滤函数类型存储
    • null值特殊处理避免类型冲突
  5. 完整API支持
    • 保持原生localStorage的API风格(setItem/getItem/removeItem/clear)

使用建议:

// 存储时明确类型
TypedStorage.setItem<YourType>('key', value)

// 读取时指定类型
const value = TypedStorage.getItem<YourType>('key')

// 处理null情况
const value = TypedStorage.getItem<YourType>('key') ?? defaultValue

此方案在保留localStorage原生API特点的同时,通过TypeScript类型系统提供了更安全的类型保障,且自动处理了数据类型转换问题。

读书