应用端实验室桌面应用
00 / 00

本地存储

区分桌面应用里的前端状态、app data、本地文件、Keychain 和后端数据

你将学到

  • 桌面应用里有哪些存储位置,各自适合放什么数据
  • 模板当前保存了什么,为什么用 app data 而不是 localStorage
  • 敏感凭证该放哪里
  • 本地数据迁移怎么设计
  • 本地存储和后端数据的边界在哪

桌面应用很容易把"本地存储"混成一坨。模板只默认保存本地偏好,其他数据要按安全性和生命周期选存储位置。

存储位置对比

桌面端有六种常见的数据存储位置。选错了会导致数据丢失、安全漏洞或迁移困难:

存储位置适合放什么不适合放什么生命周期
React state / TanStack Query页面临时状态、接口缓存需要重启后保留的数据当前页面会话
localStorage浏览器预览 fallback、小型非敏感偏好token、secret、重要业务数据浏览器生命周期
Tauri app data桌面偏好、草稿、缓存索引、本地配置明文密钥和高价值 secret应用安装期间
OS Keychain / Credential Storerefresh token、第三方 API key、敏感凭证大文件和普通 UI 状态用户账号级别
用户选择的文件夹用户明确要保存的导出文件应用内部配置用户决定
后端数据库账号、订单、权限、协作数据只属于本机的 UI 偏好服务端管理

React state 在页面关闭后就没了。localStorage 在清缓存或换 WebView 后可能丢失。app data 目录跟着应用走,重装才清。Keychain 是操作系统级别的安全存储,应用卸载后在某些系统上仍保留。

模板的双通道存储

模板用了一个巧妙的策略:浏览器预览和 Tauri 原生窗口用不同的存储后端,但对上层组件暴露同一个接口。

在浏览器预览模式下,preferences.ts 读写 localStorage。在 Tauri 原生窗口下,同样的函数通过 invokeTauri() 调用 Rust command,读写 app data 目录。

这意味着你写 UI 时不需要判断当前环境,adapter 自己处理 fallback。组件调 getDesktopPreferences() 就行,底层是 localStorage 还是 app data 不影响 UI 逻辑。

模板当前保存什么

模板只保存桌面偏好,类型定义如下:

export type DesktopPreferences = {
  autoUpdateCheck: boolean;
  launchAtLogin: boolean;
  serverUrl: string;
  startMinimized: boolean;
  version: number;
  webUrl: string;
};

浏览器预览时保存在 localStorage。Tauri 原生窗口里保存在 app data 目录的 desktop-preferences.json

Rust 侧代码在 products/01mvp/apps/desktop/src-tauri/src/lib.rs,TypeScript adapter 在 products/01mvp/apps/desktop/src/runtime/preferences.ts

为什么不用 localStorage 存一切

桌面 WebView 里的 localStorage 放简单前端状态可以,但不适合当通用桌面存储:

  • 没法做版本迁移 -- 没有内置的 schema evolution 机制
  • 不适合放敏感凭证 -- 明文存在 WebView 存储里,任何有文件权限的进程都能读
  • 不适合大文件或缓存目录 -- 键值对格式,没有目录结构
  • 用户不好定位和排查 -- macOS 上藏在 ~/Library/WebKit/ 下,路径不直观
  • 换 WebView、清缓存或重装后行为不直观 -- 数据可能悄悄丢失

稳定的桌面配置优先放 app data。敏感凭证走 OS Keychain。

app data 适合什么

app data 目录适合放可删除后重新生成的数据:

  • 用户设置和桌面偏好
  • 本地缓存索引
  • 最近打开的项目列表
  • 非敏感草稿
  • 本地任务状态
  • 日志和诊断数据

模板里可以通过设置面板打开 app data 目录,方便用户理解数据保存在哪里。这个入口对调试也很有用 -- 用户遇到问题时可以手动检查或清理数据。

敏感凭证怎么放

别把这些明文丢进 JSON 文件:

  • 第三方 API key
  • refresh token
  • 支付密钥
  • 数据库连接串
  • 管理员令牌

如果产品需要本地保存敏感凭证,加 OS Keychain / Credential Store 插件,通过 src/runtime 封装。前端组件只调 adapter,不直接操作插件。

Keychain 在不同系统上的表现:macOS 用 Keychain Access,Windows 用 Credential Manager,Linux 用 libsecret 或 GNOME Keyring。Tauri 有社区 plugin 封装了这些差异,不需要你手写平台代码。

数据迁移

本地偏好带 version 字段,用于以后升级结构:

const DESKTOP_PREFERENCES_VERSION = 1;

以后修改结构时按版本迁移:

  1. 读取旧 JSON
  2. 根据旧 version 补齐默认值或转换字段
  3. 写回新版本
  4. 保留 fallback,避免旧文件损坏导致 App 打不开

模板当前的 normalizeDesktopPreferences() 已经处理空值和默认值。新增字段时只需要在 normalize 函数里补一行默认值。

举个例子:如果 v2 加了一个 theme 字段,normalize 函数检查 version < 2 时补上 theme: "system"。旧用户升级后不会因为缺字段报错。

永远假设旧 JSON 文件可能损坏、格式不对或字段缺失。normalize 函数要能处理任意输入,返回一个完整的有效对象。

常见错误

新手最容易犯的存储错误:

  • 把 auth token 存进 localStorage -- 明文暴露,清缓存后用户要重新登录
  • 把用户设置存进 React state -- 刷新页面就丢了
  • 把后端返回的权限列表缓存在本地当判断依据 -- 过期后用户可能越权
  • 把大文件直接写进 app data -- 没有清理机制,磁盘越占越多
  • 忘记处理 JSON 损坏 -- 用户手动改文件或磁盘错误后白屏

和后端数据的边界

账号、付费状态、订单、权限和协作数据放后端。本地只缓存展示用的数据,不能把本地缓存当权限判断来源。

几条原则:

  • 权限和计费以服务端为准 -- 本地缓存的权限信息过期后必须失效
  • 本地偏好以 app data 为准 -- 重启后不丢
  • 敏感凭证走 Keychain -- 不进 JSON、不进 git
  • 大文件由用户选择目录或使用专门缓存目录 -- 不要塞进 app data
  • 可从后端重新拉取的数据可以缓存,但要能失效 -- 网络恢复后刷新

验收

修改本地存储逻辑后,至少检查这些:

  • 浏览器预览 fallback 可用
  • Tauri 原生窗口能读写 app data
  • JSON 损坏时应用不会白屏
  • 改默认值后旧配置能正常补齐
  • 退出重开后设置仍然存在
  • 删除 app data 文件后能恢复默认配置

下一步

数据存对了位置,下一步去看 托盘与窗口,了解桌面应用的窗口行为和系统托盘设计。

想和其他创造者交流?

这篇文档有问题?