Lançado · em melhoria
Guia de Electron · 4/6
Por enquanto, este capítulo está disponível apenas em inglês.
Desktop apps keep data on the user's machine: settings, recent files, and documents. This chapter covers where to store data, native APIs such as file dialogs and notifications, and how to split state between renderers and the main process.
Each operating system keeps app data in a different place; app.getPath() returns the right directory. App-specific data belongs in userData.
| Name | Purpose |
|---|---|
userData | App settings and data; by default a folder named after the app |
documents | The user's documents folder |
downloads | The downloads folder |
temp | Temporary files |
logs | The app's log folder |
const { app } = require('electron')
app.whenReady().then(() => {
console.log(app.getPath('userData'))
// Windows: C:\Users\<user>\AppData\Roaming\<app name>
// macOS: ~/Library/Application Support/<app name>
// Linux: ~/.config/<app name>
})Never write user data next to the app itself (app.getAppPath() or the executable folder). After packaging, that location may be read-only or replaced on update.
A single JSON file is enough for small settings. Do the file access in the main process and give renderers only read and write operations over IPC. Writing to a temporary file and renaming it keeps the file intact if the app quits mid-write.
// main.js
const { app, ipcMain, BrowserWindow } = require('electron')
const fs = require('node:fs/promises')
const path = require('node:path')
const settingsFile = () => path.join(app.getPath('userData'), 'settings.json')
const defaults = { theme: 'system', recentFiles: [] }
async function loadSettings() {
try {
return { ...defaults, ...JSON.parse(await fs.readFile(settingsFile(), 'utf8')) }
} catch {
return { ...defaults }
}
}
async function saveSettings(settings) {
const tmp = `${settingsFile()}.tmp`
await fs.writeFile(tmp, JSON.stringify(settings, null, 2))
await fs.rename(tmp, settingsFile())
}
ipcMain.handle('settings:get', () => loadSettings())
ipcMain.handle('settings:setTheme', async (_event, theme) => {
if (!['light', 'dark', 'system'].includes(theme)) throw new Error('invalid theme')
const settings = { ...(await loadSettings()), theme }
await saveSettings(settings)
// Tell every open window about the change
for (const win of BrowserWindow.getAllWindows()) win.webContents.send('settings:changed', settings)
return settings
})For a key-value store, consider the community package ; for larger data, an embedded database such as SQLite. Native modules like must be rebuilt against Electron, which Forge does for you (otherwise run ).
electron-storebetter-sqlite3npx @electron/rebuildThe dialog module opens native dialogs: showOpenDialog, showSaveDialog, and showMessageBox for confirmations. Pass a window as the first argument to make the dialog modal to it.
const { dialog } = require('electron')
ipcMain.handle('file:save', async (event, content) => {
if (typeof content !== 'string') throw new Error('invalid content')
const win = BrowserWindow.fromWebContents(event.sender)
const { canceled, filePath } = await dialog.showSaveDialog(win, {
defaultPath: path.join(app.getPath('documents'), 'note.txt'),
filters: [{ name: 'Text', extensions: ['txt', 'md'] }],
})
if (canceled || !filePath) return false
await fs.writeFile(filePath, content, 'utf8')
return true
})Notification in the main process posts to the system notification center; renderers can also use the web Notification API. On Windows, notifications need an Application User Model ID; installers usually set it, and in development you can call app.setAppUserModelId().
Encrypt sensitive strings such as passwords or access tokens with safeStorage, which uses the operating system's key storage, before writing them to disk.
const { Notification, safeStorage } = require('electron')
function notifyDone(fileName) {
if (Notification.isSupported()) {
new Notification({ title: 'Export complete', body: `Saved ${fileName}.` }).show()
}
}
async function saveToken(token) {
if (!safeStorage.isEncryptionAvailable()) throw new Error('encryption unavailable')
const encrypted = safeStorage.encryptString(token) // Buffer
await fs.writeFile(path.join(app.getPath('userData'), 'token.bin'), encrypted)
}
async function loadToken() {
const encrypted = await fs.readFile(path.join(app.getPath('userData'), 'token.bin'))
return safeStorage.decryptString(encrypted)
}A renderer handles state like any web app. Keep UI state in your framework's state tools (React, Vue, Svelte, and so on), small values in localStorage, and larger data in IndexedDB. With the default session these stores live under userData, so they survive restarts.
When several windows must see the same data, make the main process the single source of truth. Renderers request values over IPC, and the main process broadcasts changes to every window.
// preload.js
contextBridge.exposeInMainWorld('settings', {
get: () => ipcRenderer.invoke('settings:get'),
setTheme: (theme) => ipcRenderer.invoke('settings:setTheme', theme),
onChanged: (callback) => {
ipcRenderer.on('settings:changed', (_event, value) => callback(value))
},
})
// renderer.js
const apply = (settings) => { document.documentElement.dataset.theme = settings.theme }
window.settings.get().then(apply)
window.settings.onChanged(apply)app.getPath('userData'), never in the installation folder.dialog, Notification, and safeStorage for dialogs, notifications, and encrypting sensitive values.
0 comentários
Fazer login · Faça login para deixar um comentário.
Seja o primeiro a comentar.