【HarmonyOS学习笔记】2026-07-10 | 保存应用数据(Day 2)

发布时间:2026/7/12 17:50:44
【HarmonyOS学习笔记】2026-07-10 | 保存应用数据(Day 2) 【HarmonyOS学习笔记】2026-07-10 | 保存应用数据Day 2date: 2026-07-10tags: [HarmonyOS, 保存应用数据, export, import, 单例模式, StorageLink, StorageProp, AppStorage, Preferences, 模块缓存, 模板字符串]1️⃣ 学习内容概览延续保存应用数据章节今天聚焦三个层面模块导出机制export default vs export 命名导出、模块缓存与单例、全局状态管理StorageLink/StorageProp 与 AppStorage、数据持久化架构Preferences → AppStorage → StorageLink 三层关系与数据流。同时发现并修正了 hilog 模板字符串语法错误。2️⃣ 核心知识点与实操问题一、模块导出机制export default vs export两种导出方式导出方式写法导入方式导出的是什么命名导出export class PreferencesUtil { }import { PreferencesUtil } from 路径类/类型可导出多个默认导出export default new PreferencesUtil()import PreferencesUtil from 路径一个实例只能导出一个默认记忆方法有{} “我要这个模块里指定的某个东西”没{} “我要这个模块的默认那个东西”项目中的实际对比// 命名导入带花括号 → 导入的是类/模块import{BusinessError}fromkit.BasicServicesKit;import{preferences}fromkit.ArkData;import{hilog}fromkit.PerformanceAnalysisKit;// 默认导入不带花括号 → 导入的是实例importPreferencesUtilfrom../common/database/PreferencesUtil;EntryAbility.ets 中的注释也印证了这一点// 这个引用是引用实例带{}的是引用类。两种导出能否同时存在可以。一个文件可以同时有命名导出和默认导出exportclassPreferencesUtil{...}// 命名导出导出类exportdefaultnewPreferencesUtil();// 默认导出导出实例但对于 PreferencesUtil 这种全局唯一的工具类教材选择只用export default new导出单例实例就够了不需要导出类。二、模块缓存机制与单例模式模块只执行一次模块在第一次被导入时执行之后再导入直接返回缓存结果// PreferencesUtil.etsexportdefaultnewPreferencesUtil();// ← new 只执行一次// A.etsimportpreferencesUtilfrom../common/database/PreferencesUtil;// 第一次导入执行 new// B.etsimportpreferencesUtilfrom../common/database/PreferencesUtil;// 不是第一次拿缓存不会再次 newA.ets 和 B.ets 中的preferencesUtil是同一个对象共享同一个preference属性。单例模式的天然保障export default new PreferencesUtil()本质就是单例模式全局只有一个 PreferencesUtil 实例所有模块拿到的都是同一个对象Preferences 的 preference 属性全局共享不会出现 A 存了数据 B 读不到的情况这就是为什么教材只导出实例不导出类——不需要也不应该创建第二个 PreferencesUtil 实例。三、StorageLink 与 StorageProp全局状态绑定StorageLink双向绑定定义将组件的状态变量与AppStorage中的指定 key 建立双向同步。StorageLink(fontSizeOffset)fontSizeOffset:number0;// ↑ 装饰器 ↑ AppStorage的key ↑ 变量名 ↑ 类型 ↑ 默认值双向同步的含义组件修改 StorageLink 变量 → AppStorage 更新 → 其他绑定了同一 key 的组件也更新 AppStorage 修改该 key → 组件的 StorageLink 变量更新前提AppStorage 中必须先有这个 keyAppStorage.setOrCreate(fontSizeOffset,0);StorageProp单向绑定定义将组件的状态变量与AppStorage中的指定 key 建立单向同步AppStorage → 组件。StorageProp(fontSizeOffset)fontSizeOffset:number0;组件可以读取 AppStorage 中的值但修改不会同步回 AppStorage。对比StorageLinkStorageProp方向双向组件 ↔ AppStorage单向AppStorage → 组件组件修改会同步到 AppStorage不会同步到 AppStorageAppStorage 修改会同步到组件会同步到组件类比双向车道单行道场景需要修改全局数据的组件如设置页只读全局数据的组件如展示页四、数据持久化架构Preferences → AppStorage → StorageLink三层架构Preferences持久化存储文件 ←→ AppStorage内存全局状态 ←→ StorageLink组件UI层作用生命周期数据位置Preferences数据持久化到文件应用卸载前一直存在文件系统AppStorage内存中的全局状态应用运行期间内存StorageLink组件与 AppStorage 的双向绑定组件生命周期组件内完整数据流1. 应用启动 PreferencesUtil.getFontPreferences(context) → 初始化 Preference 对象 2. 导航到 MainPageaboutToAppear this.fontSizeOffset PreferencesUtil.getChangeFontSize() → 读取 Preferences 中的值 → 赋给 StorageLink 变量 → 自动写入 AppStorage 3. Home / Setting 组件渲染 StorageLink(fontSizeOffset) → 从 AppStorage 取值 → UI 显示 4. 用户修改Slider 确定按钮 this.fontSizeOffset this.currentValue → StorageLink 变量更新 → AppStorage 自动更新 → 其他组件同步 PreferencesUtil.saveChangeFontSize(this.currentValue) → putSync flush → 写入文件 5. 应用重启 回到步骤1从文件恢复数据关键发现2026-07-11 实测验证教材项目的 Preferences → AppStorage 桥接不是通过AppStorage.setOrCreate而是通过 MainPage.aboutToAppear 中给 StorageLink 变量赋值// MainPage.ets — 这就是桥接StorageLink(fontSizeOffset)fontSizeOffset:number0;aboutToAppear():void{this.fontSizeOffsetPreferencesUtil.getChangeFontSize();// 赋值 写入 AppStorage}StorageLink 的隐式机制给 StorageLink 变量赋值 自动写入 AppStorage双向绑定的特性不需要显式调用AppStorage.setOrCreate。StorageLink 初始化行为AppStorage 中已有该 key → 取 AppStorage 中的值忽略默认值AppStorage 中没有该 key → 用默认值在 AppStorage 中创建该 key注意多个组件的 StorageLink 绑定同一 key 但默认值不同时谁先初始化谁的默认值先写入 AppStorage。本项目中 Home 默认 0、SliderCustomDialog 默认 4、MainPage 默认 0但由于 MainPage 的 aboutToAppear 紧跟初始化用 Preferences 真实值覆盖所以不受影响。当前项目的桥接方式环节实现方式代码位置Preferences → AppStorageMainPage.aboutToAppear 中this.fontSizeOffset getChangeFontSize()MainPage.ets:22-24AppStorage → StorageLinkStorageLink 双向绑定自动同步Home/Setting/SliderCustomDialogStorageLink → PreferencesSlider 确定按钮saveChangeFontSize()this.fontSizeOffset ...SliderCustomDialog.ets:59-62实测验证日志LoginPage: AppStorage fontSizeOffset undefined ← MainPage 还没加载 Home: AppStorage fontSizeOffset -2 ← MainPage.aboutToAppear 已桥接 Preferences: fontSizeOffset -2 ← 值一致区分性实验将 SliderCustomDialog 默认值从 4 改为 99重启后 Home 显示 -2Preferences 值而非 99确认值来自 Preferences 桥接而非组件默认值覆盖。持久化文件存储位置代码中preferences.getPreferencesSync(context, { name: FontPreferences })只给了一个name没有路径定义。文件存在哪开发者不能自定义路径——Preferences API 只接受name参数系统自动管理文件位置。实测验证2026-07-11打印context.preferencesDir得到/data/storage/el2/base/haps/entry/preferences这是沙箱路径应用视角映射到手机文件系统的真实路径沙箱路径应用看到的真实路径手机文件系统/data/storage/el2/base/haps/entry/preferences/data/app/el2/100/base/bundleName/haps/entry/preferences/完整文件路径.../preferences/FontPreferences文件名 代码中传的 name。各部分含义el2 加密等级2设备解锁后可访问100 用户ID多用户主用户entry 模块名FontPreferences{ name: FontPreferences }中的 namecontext 的作用决定文件存放在哪个应用/模块的沙箱下不同应用的 Preferences 互不可见。查看方式DevEco Studio → Device File Explorer → 导航到对应路径。我的吐槽“不用保密的保密文件”——系统用沙箱加密隔离保护目录结构不被随意访问但文件内容如字体大小偏移值毫无机密可言。安全机制保护的是访问权限不是内容本身。五、hilog 模板字符串语法修正问题代码中单引号包裹的${}不会插值// ❌ 单引号 普通字符串${} 不会插值hilog.error(DOMAIN,TAG,create failed,code${err.code},message ${err.message});// ✅ 反引号 模板字符串${} 才会插值hilog.error(DOMAIN,TAG,create failed,code${err.code},message ${err.message});// ✅ 或者用字符串拼接hilog.error(DOMAIN,TAG,create failed,codeerr.code,messageerr.message);规则${}插值语法只在反引号模板字符串中生效单引号和双引号中${}只是普通字符。3️⃣ 为什么这些问题出现刨根问底 我的理解export default new 单例模式 模块缓存的天然配合export default new PreferencesUtil()不是一个随意的写法而是利用了模块缓存机制来保证单例。模块首次导入时执行new后续导入拿缓存——不需要额外的单例管理代码。这也解释了为什么教材只导出实例不导出类全局数据存储天然应该是单例不需要第二个实例。Preferences / AppStorage / StorageLink 三层各有分工Preferences 负责活得久持久化到文件重启还在AppStorage 负责传得广内存全局状态所有组件共享StorageLink 负责绑得紧组件自动同步。三者不能互相替代——只有 Preferences 不能自动同步 UI只有 StorageLink 重启就丢了。三层配合才是完整的数据持久化方案。StorageLink 和 StorageProp 的选择依据不是都能用就选更灵活的而是看组件的角色。设置页需要修改全局数据用 StorageLink展示页只需要读取用 StorageProp。选错了不会报错但会让数据流方向不清晰增加维护成本。抄代码不能抄完就完事本次 MainPage.aboutToAppear 中的this.fontSizeOffset getChangeFontSize()是教材给的关键桥接代码但我们抄了没深究它为什么在这、做了什么后面调查时只盯着 EntryAbility 找绕了一大圈。抄代码时每行都要问它在整条数据链中的位置和作用比事后排查省十倍时间。4️⃣ 实操验证记录#问题现象排查过程解决1import 加 {} 报错import { PreferencesUtil }找不到对比其他导入方式确认 export default 不加 {}默认导出不加花括号命名导出才加2多次导入是否多次创建担心多个模块导入会创建多个实例了解模块缓存机制模块只执行一次后续导入拿缓存3StorageLink 含义不理解装饰器的完整语义查阅定义和用法双向绑定 AppStorage 的 key组件修改会同步4hilog ${} 不插值日志输出原样${err.code}而不是实际值检查引号类型单引号不插值需要反引号或字符串拼接5Preferences→AppStorage 桥接在哪杀进程重启后 StorageLink 能拿到 Preferences 值但代码没有 AppStorage.setOrCreate1. 在 LoginPage/Home 加 AppStorage.get 日志 2. 将 SliderCustomDialog 默认值改为 99 做区分实验MainPage.aboutToAppear 中this.fontSizeOffset getChangeFontSize()赋值 写入 AppStorage6StorageLink 赋值的隐式效果不确定给 StorageLink 变量赋值是否会同步到 AppStorage实测LoginPage 时 AppStorageundefinedMainPage.aboutToAppear 后 AppStoragePreferences值StorageLink 是双向绑定赋值 自动写入 AppStorage不需要显式 setOrCreate7Preferences 文件存在哪代码只给 name 没给路径不知道文件位置打印context.preferencesDir得到沙箱路径系统自动管理开发者不能自定义路径5️⃣ 最终结论最佳实践维度建议默认导出 vs 命名导出单例工具类用export default new不加 {}类/类型用export class加 {}导入对应export default→import X from无 {}export→import { X } from有 {}单例保障export default new 模块缓存 天然单例不需要额外管理StorageLink双向绑定 AppStorage组件修改会同步到全局用于需要修改全局数据的组件StorageProp单向绑定 AppStorage组件修改不同步到全局用于只读全局数据的组件StorageLink 赋值给 StorageLink 变量赋值 自动写入 AppStorage不需要显式AppStorage.setOrCreateStorageLink 初始化AppStorage 无该 key 时用默认值创建有该 key 时取 AppStorage 值忽略默认值默认值一致性多个组件 StorageLink 同一 key 时默认值应保持一致否则初始化顺序影响结果数据持久化三层Preferences文件持久化→ AppStorage内存全局→ StorageLink组件UI完整数据流启动MainPage.aboutToAppear 赋值桥接 Preferences→AppStorage修改StorageLink→AppStorage 自动同步 saveChangeFontSize 持久化Preferences 文件位置开发者不能自定义路径系统自动管理context.preferencesDir可查看沙箱路径文件名 代码中传的 name沙箱路径映射沙箱/data/storage/el2/base/haps/entry/preferences→ 真实/data/app/el2/100/base/bundleName/haps/entry/preferences/模板字符串${}插值必须用反引号单引号/双引号中${}是普通字符// 单例工具类导出模式classPreferencesUtil{preference?:preferences.Preferences;// ... 方法}exportdefaultnewPreferencesUtil();// 导出单例实例// 导入时不加花括号importPreferencesUtilfrom../common/database/PreferencesUtil;// ✅// import { PreferencesUtil } from ../common/database/PreferencesUtil; // ❌// 数据持久化三层架构 — 桥接方式// 方式1显式写入 AppStorage在 UIAbility.onCreate 中AppStorage.setOrCreate(fontSizeOffset,PreferencesUtil.getChangeFontSize());// 方式2隐式写入在 MainPage.aboutToAppear 中教材项目的实际写法StorageLink(fontSizeOffset)fontSizeOffset:number0;aboutToAppear(){this.fontSizeOffsetPreferencesUtil.getChangeFontSize();// 赋值 写入 AppStorage}// 组件中StorageLink 绑定Componentstruct Home{StorageLink(fontSizeOffset)fontSizeOffset:number0;// 双向可修改}Componentstruct DisplayPage{StorageProp(fontSizeOffset)fontSizeOffset:number0;// 单向只读}// 修改后持久化PreferencesUtil.saveChangeFontSize(this.fontSizeOffset);// putSync flush// hilog 正确写法hilog.error(DOMAIN,TAG,code${err.code}, message${err.message});// ✅ 反引号hilog.error(DOMAIN,TAG,codeerr.code, messageerr.message);// ✅ 拼接核心原则数据持久化的关键在于三层配合数据流不断——Preferences 保证数据活得久文件持久化AppStorage 保证数据传得广全局共享StorageLink 保证数据绑得紧UI 自动同步。任何一层断裂都会导致数据丢失或不同步。桥接 Preferences→AppStorage 有两种方式显式AppStorage.setOrCreate推荐在 UIAbility.onCreate 中调用或隐式给 StorageLink 变量赋值教材项目在 MainPage.aboutToAppear 中实现。模块导出的关键在于导出方式决定导入方式——export default不加{}export加{}两者不能混用。export default new利用模块缓存天然实现单例是全局工具类的推荐写法。学习小结今天学了保存应用数据的进阶部分核心收获是三方面。一是理清了模块导出机制——export default导出实例导入不加 {}export命名导出导入加 {}export default new利用模块缓存天然实现单例。二是理解了 StorageLink双向绑定和 StorageProp单向绑定的区别——前者组件修改会同步到 AppStorage后者只读不同步。三是梳理了数据持久化三层架构——Preferences文件→ AppStorage内存→ StorageLinkUI三层各有分工不能互相替代。经实测验证教材项目的 Preferences→AppStorage 桥接是通过 MainPage.aboutToAppear 中给 StorageLink 变量赋值实现的赋值 自动写入 AppStorage而非显式调用AppStorage.setOrCreate。同时确认了 Preferences 文件存储位置——开发者不能自定义路径系统自动管理在沙箱/data/storage/el2/base/haps/entry/preferences下文件名等于代码中传的 name可通过context.preferencesDir查看。这属于不用保密的保密文件——安全机制保护的是访问权限不是内容本身。同时修正了 hilog 模板字符串的语法错误单引号中 ${} 不插值需用反引号。懿路向前 · AI辅助整理2026-07-10