场景

有时候我们需要在主进程和渲染层共享某些数据,而 electron ipc 通信 显然更适合传递消息而不适合共享数据。

相关依赖

事实上,我们这个需求已经有人考虑过了,例如 electron-store 就已经实现了可以在渲染层、主进程均可使用。

那么,我们直接用 electron-store 有什么问题么? 是的,electron 仅能在 electron 中使用,所以在浏览器上会报错,而这对于开发环境而言是无法接受的,故而还需要检测环境使用不同的实现。

创建浏览器兼容层

使用策略模式实现不同环境下使用不同的存储

  • 浏览器使用 localStorage 实现
  • electron 中则使用 electron-store 实现
import type Store from 'electron-store'
import isElectron from 'is-electron'
import { DeepReadonly } from 'utility-types'

interface BaseStore {
  get(key: string): string | null

  set(key: string, value: string): void
}

class ElectronStoreImpl implements BaseStore {
  private store: Store

  constructor() {
    const Store = window.require('electron-store')
    this.store = new Store()
  }

  set(key: string, value: string): void {
    this.store.set(key, value)
  }

  get(key: string) {
    return this.store.get(key) as string
  }
}

class LocalStorageImpl implements BaseStore {
  get(key: string) {
    return localStorage.getItem(key)
  }

  set(key: string, value: string) {
    localStorage.setItem(key, value)
  }
}

const symbol = Symbol('ElectronStore.store')

export class ElectronStore {
  private readonly [symbol]: BaseStore = isElectron()
    ? new ElectronStoreImpl()
    : new LocalStorageImpl()

  static getInstance<T extends object>(
    init?: Partial<T>,
  ): Partial<{ [P in keyof T]: DeepReadonly<T[P]> }> {
    const electronStore = new ElectronStore()

    const proxy = new Proxy({} as any, {
      get(target: any, p: string): any {
        const text = electronStore[symbol].get(p)
        try {
          if (text === null || text === undefined) {
            return null
          }
          return JSON.parse(text)
        } catch (e) {
          return null
        }
      },
      set(target: any, p: string, value: any): boolean {
        electronStore[symbol].set(
          p,
          value !== undefined && value !== null ? JSON.stringify(value) : value,
        )
        return true
      },
    })
    if (init) {
      Object.entries(init).forEach(([k, v]) => {
        const text = electronStore[symbol].get(k)
        if (text === null || text === undefined) {
          proxy[k] = v
        }
      })
    }
    return proxy
  }
}

你可能发现了,为了简化使用的 API,这里使用了代理模式拦截了对实例的访问,修改为使用 get/set 方法取值和设值。

使用

使用起来和一个普通的对象没什么区别,直接通过 . 访问或设置属性即可。

const userStore = ElectronStore.getInstance<{ name: string; age: number }>()
userStore.name = 'liuli'
console.log(userStore.name === 'liuli')
userStore.age = 1
console.log(userStore.age === 1)

具体代码在 electron_exampleopen in new window,由于是一个浅层封装,所以并未发布,但可以直接将模块复制到项目中使用。