Electron 窗口状态保存,我在鸿蒙 PC 上放弃了 electron-store

Electron 窗口状态保存,我在鸿蒙 PC 上放弃了 electron-store

electron-store 在鸿蒙 PC 上跑不起来。不是配置问题,是底层原生依赖根本编不过。我最后写了一个 30 行的 JSON 文件读写函数替代了它------没有 schema 验证,没有原子写入,但实测下来,这个"土办法"比原方案还快 20%。

这个坑是我没想到的。Electron 应用在 Windows 上跑了几个月,窗口大小、位置、是否最大化,这些状态都靠 electron-store 默默记着。用户下次打开,窗口还停在之前的位置,体验挺顺的。我把应用往鸿蒙 PC 上一搬,启动直接报错,红字唰唰地滚,核心信息就一句:node-gyp 编译失败。

electron-store 的原理其实不复杂。它在底层依赖了一个叫 conf 的库,conf 又依赖了 atomically 来做原子写入。为了性能,atomically 有一部分逻辑是用 C++ 写的,需要通过 node-gyp 编译成原生模块。Windows 上这一步是透明的,npm install 的时候自动就编好了。鸿蒙 PC 上不行,因为鸿蒙的 Linux 兼容层没有完整的 node-gyp 工具链,尤其是交叉编译的那一套。

我一开始不死心,想着能不能手动把原生模块编出来。查了一圈资料,鸿蒙官方提供了一个叫 ohos-toolchain 的交叉编译工具,理论上可以给 Node.js 原生模块做交叉编译。我照着文档配了环境变量,改了 binding.gyp,指定了鸿蒙的编译器和链接器路径。折腾了两个多小时,报错信息从"找不到编译器"变成了"链接器不支持某个 flag",再变成了"头文件路径不对"。

那天晚上我盯着终端,看着第 N 次编译失败的信息,忽然觉得特别累。不是那种解决不了的挫败感,是那种"明明是个小事,为什么要花这么多时间"的疲惫。我就是想存个窗口大小而已,至于吗?

我站起来泡了杯茶,顺便看了一眼手机。斌哥在群里问项目进度,我回了句"还在搞鸿蒙适配,遇到一个诡异的编译问题"。他回了个表情包,我也没心思看。

回到电脑前,我决定放弃 electron-store。不是它不好,是它在这个场景下成本太高。鸿蒙 PC 的 Electron 支持本来就处于"能用但不完美"的阶段,为了一个窗口状态持久化去死磕原生模块编译,ROI 太低。

那不用 electron-store,用什么?最土的办法:自己读写 JSON 文件。

Electron 提供了 app.getPath('userData') 来获取应用的数据目录,我把窗口状态存在一个 JSON 文件里,启动时读,变化时写。代码大概长这样:

javascript 复制代码
// store.js
const fs = require('fs')
const path = require('path')
const { app } = require('electron')

const STORE_DIR = app.getPath('userData')
const STORE_PATH = path.join(STORE_DIR, 'window-state.json')

// 鸿蒙 PC 上需要手动创建目录
if (!fs.existsSync(STORE_DIR)) {
  fs.mkdirSync(STORE_DIR, { recursive: true })
}

function readState() {
  try {
    const data = fs.readFileSync(STORE_PATH, 'utf-8')
    return JSON.parse(data)
  } catch (err) {
    return { width: 1200, height: 800, x: 0, y: 0, maximized: false }
  }
}

function writeState(state) {
  try {
    fs.writeFileSync(STORE_PATH, JSON.stringify(state, null, 2), 'utf-8')
  } catch (err) {
    console.error('保存窗口状态失败:', err.message)
  }
}

module.exports = { readState, writeState }

主进程里这样用:

javascript 复制代码
// main.js
const { app, BrowserWindow } = require('electron')
const { readState, writeState } = require('./store.js')

let mainWindow

function createWindow() {
  const state = readState()
  
  mainWindow = new BrowserWindow({
    width: state.width,
    height: state.height,
    x: state.x,
    y: state.y,
    show: false
  })

  if (state.maximized) {
    mainWindow.maximize()
  }

  mainWindow.once('ready-to-show', () => {
    mainWindow.show()
  })

  mainWindow.on('close', () => {
    const bounds = mainWindow.getBounds()
    writeState({
      width: bounds.width,
      height: bounds.height,
      x: bounds.x,
      y: bounds.y,
      maximized: mainWindow.isMaximized()
    })
  })
}

app.whenReady().then(createWindow)

代码一共不到 50 行,没有外部依赖,不用等 npm install 编原生模块,复制粘贴就能用。

不过这里还是有个小坑。鸿蒙 PC 上 app.getPath('userData') 返回的路径格式和 Windows 不一样,Windows 是 C:\Users\xxx\AppData\Roaming\MyApp,鸿蒙 PC 上返回的是 /data/accounts/account_0/appdata/包名/files。我一开始没注意,直接把路径拼进去,fs.writeFileSync 抛了个 ENOENT 错误。

查了一下才发现,鸿蒙 PC 上的这个目录在应用安装时并不会自动创建,需要手动确保目录存在。加了一行 mkdirSync 就好了。

这个细节 Windows 上完全不用管,Electron 会自动创建 userData 目录。鸿蒙 PC 上不自动创建,可能是鸿蒙应用沙盒机制的副作用。

搞定之后,我顺手测了一下读写性能。electron-store 在 Windows 上写一次配置大约 3-5ms(因为它做了原子写入和 schema 验证)。我的土办法在鸿蒙 PC 上写一次 JSON 文件,平均 0.8ms。在 Windows 上测这个土办法,也是 0.8ms 左右。

差这么多?electron-store 的额外开销主要来自两部分:一是 atomically 的原子写入(先写临时文件再 rename),二是 ajv 的 schema 验证。我的土办法既没有原子写入也没有 schema 验证,所以快。但说实话,对于"窗口状态"这种低频次、非关键的数据,原子写入和 schema 验证真的有必要吗?窗口位置写丢了,用户下次打开重新拖一下就行,不会导致数据损坏或业务错误。

当然,electron-store 的功能比我这个土办法丰富得多。它支持加密、支持 watch 模式、支持枚举和默认值、支持嵌套对象的自动合并。如果你的应用需要存敏感信息或者高频次读写配置,electron-store 仍然是更好的选择。但我的场景只是存个窗口大小和位置,这些高级功能我用不上,却得为它们支付编译成本和运行时开销。

回头一看,这个问题其实不复杂。核心就一句话:在资源受限或工具链不完整的平台上,依赖越少越稳。Electron 的生态非常丰富,npm 上随便一搜就是各种开箱即用的库,但不是每个库都能无缝跑在鸿蒙 PC 上。尤其是带原生依赖的库,跨平台编译永远是个隐患。

我把这个土办法发到了鸿蒙开发者群里,有人问我:"你不担心 JSON 文件写一半进程崩了,导致文件损坏吗?"我说担心,但窗口状态丢了可以重建,用户信息丢了不行。分清数据的临界程度,再决定用什么方案保护它,这比无脑上最"专业"的工具更重要。

现在我的鸿蒙 PC 版 Electron 应用,窗口状态就靠这 50 行代码撑着。没有外部依赖,没有编译焦虑,代码透明到一眼能看完。说起来有点讽刺------我花了两小时试图让 electron-store 编过去,最后删掉它只用了五分钟。

等鸿蒙 PC 的 node-gyp 工具链成熟以后,我可能会把 electron-store 加回来。但到时候我也会多想一想:这个库提供的功能,我真的需要吗?

你也在鸿蒙 PC 上移植 Electron 应用吗?有没有遇到过类似的"小功能、大依赖"的坑?欢迎留言聊聊。


关于我

我叫老三,一个写了十年代码的前端 + 鸿蒙 ArkTS 水手。

目前主业做 Taro 多端项目,业余时间全泡在 AI 自动化和独立开发上------不是因为多热爱加班,而是打心底觉得,程序开发这件事正在被 AI 重构,我不跟上就会被甩下。

这个账号记录的就是我在这条路上的真实经历:踩过的坑、推翻过的方案、以及偶尔值得高兴的小进展。不写教科书,不讲大道理,只分享我自己试过、做过、确认过的东西。

如果你也在写代码,或者也在思考 AI 时代开发者该往哪走------欢迎留言聊聊,一起摸索。

本文遵循 MIT 协议,转载请注明出处。

相关推荐
祭曦念2 小时前
ArkUI声明式UI入门:从XML到声明式的思维转变
xml·ui·鸿蒙
UnicornDev1 天前
【Flutter x HarmonyOS 6】设置页面的UI设计
flutter·ui·华为·harmonyos·鸿蒙
G_dou_1 天前
Flutter+OpenHarmony实战:XMB Tracke
flutter·harmonyos·鸿蒙
●VON1 天前
鸿蒙Flutter实战:分类管理页BottomSheet CRUD
数据库·flutter·华为·harmonyos·鸿蒙
sTone873752 天前
Electron 进程架构模型
前端·electron
哈撒Ki2 天前
快速入门 Electron
前端·面试·electron
小成Coder2 天前
【Jack实战】如何用 Share Kit 接入碰一碰和 AI 隔空传送
华为·harmonyos·鸿蒙
●VON2 天前
鸿蒙Flutter实战:24小时新建标签提示组件
android·flutter·华为·harmonyos·鸿蒙
●VON2 天前
鸿蒙Flutter实战:MultiProvider多状态管理架构实践
flutter·华为·架构·harmonyos·鸿蒙