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 协议,转载请注明出处。