数据导出、备份与恢复

本文是《HarmonyOS NEXT 企业级开发实战:30篇打造智能记账APP》系列的第 25 篇,对应 Git Tag v0.2.5。承接上一篇设置中心开发,本篇深入讲解 SettingView 中的 exportData 数据导出方法与 doClear 数据清空实现,并扩展探讨本地备份与数据恢复的完整设计方案。

前言

数据是记账应用的核心资产。用户产生的每一笔账单、每一个分类都应该可以被导出、备份和恢复。企业级应用必须让用户掌控自己的数据,而不是将数据锁死在应用内部。本文将带你:

  1. 深入解析 SettingViewexportData 方法的实现细节
  2. 设计 ExportData 接口约束导出数据结构
  3. 掌握 PreferenceUtil 偏好存储的读写机制
  4. 实现 doClear 数据清空与二次确认防护
  5. 扩展设计本地备份与恢复的完整方案

企业级核心原则:数据必须可导出、可备份、可恢复,同时操作必须类型安全。ArkTS 严格禁止使用 any 类型,所有数据结构通过 interface 明确定义。参考 HarmonyOS NEXT 开发者文档 了解官方约定。


一、需求分析

1.1 功能介绍

数据导出与清空功能集成在 SettingView 设置页面中,无需独立页面。导出功能将账单与分类数据序列化为 JSON 格式,清空功能通过 ConfirmDialog 二次确认后清除所有本地数据。

需求项 说明 实现位置
数据导出 读取 billscategories 偏好数据,组装为 JSON 格式 SettingView.exportData
数据清空 清除所有偏好数据,二次确认防护 SettingView.doClear
数据备份 将全量数据序列化存储为备份文件(扩展设计) PreferenceUtil.setObject
数据恢复 从备份 JSON 解析并还原数据(扩展设计) PreferenceUtil.getObject

1.2 导出格式对比

记账应用的数据导出通常支持多种格式,不同格式各有优劣。

格式 优点 缺点 适用场景
JSON 结构完整、易于解析、保留类型 体积略大 完整备份与恢复
CSV Excel 可直接打开、体积小 转义复杂、丢失类型 财务报表导出
XLSX 支持公式与样式 依赖第三方库 高级报表导出

本项目当前采用 JSON 格式导出,因为 JSON 能完整保留数据结构,便于后续恢复。

1.3 业务流程

用户进入 SettingView
  ↓
点击"导出数据"列表项
  ↓
exportData 方法执行
  ↓
PreferenceUtil.getString 读取 bills 和 categories
  ↓
组装 ExportData 接口对象
  ↓
JSON.stringify 序列化为 JSON 字符串
  ↓
ToastUtil.show 提示"数据已准备(JSON格式)"

—— 清空流程 ——
用户点击"清空所有数据"
  ↓
showClearConfirm = true → 弹出 ConfirmDialog
  ↓
用户点击"清空"确认
  ↓
doClear 方法执行
  ↓
PreferenceUtil.clear 清空所有偏好
  ↓
ToastUtil.show 提示"数据已清空"

二、数据导出核心实现

2.1 exportData 方法详解

exportDataSettingView 中的私有异步方法,负责从 PreferenceUtil 读取账单与分类数据,组装为结构化 JSON 后输出。以下是实际源码中的完整实现。

// pages/SettingView.ets - exportData 方法
private async exportData(): Promise<void> {
  const billsJson = await PreferenceUtil.getInstance().getString('bills', '[]');
  const categoriesJson = await PreferenceUtil.getInstance().getString('categories', '[]');
  interface ExportData {
    bills: string;
    categories: string;
    exportTime: string;
  }
  const data: ExportData = {
    bills: billsJson,
    categories: categoriesJson,
    exportTime: new Date().toISOString()
  };
  const json = JSON.stringify(data, null, 2);
  ToastUtil.show('数据已准备(JSON格式)');
}

2.2 ExportData 接口设计

ExportData 接口定义了导出数据的结构约束,包含三个字段。这是 ArkTS 类型安全的核心实践:用 interface 代替 any 类型,确保编译期类型检查。

interface ExportData {
  bills: string;        // 账单数据的 JSON 字符串
  categories: string;   // 分类数据的 JSON 字符串
  exportTime: string;   // 导出时间(ISO 8601 格式)
}
字段 类型 说明 默认值
bills string 账单数据 JSON 字符串 '[]'
categories string 分类数据 JSON 字符串 '[]'
exportTime string 导出时间戳 new Date().toISOString()

2.3 PreferenceUtil 数据读取

导出数据的核心是调用 PreferenceUtil.getInstance().getString() 方法读取已持久化的账单与分类数据。getString 方法接收 keydefaultValue 两个参数。

const billsJson = await PreferenceUtil.getInstance().getString('bills', '[]');
const categoriesJson = await PreferenceUtil.getInstance().getString('categories', '[]');

关键点getString 的第二个参数是默认值,当偏好中不存在对应 key 时返回该默认值。这里使用 '[]'(空数组 JSON),确保即使没有数据也能正常导出空结构。

2.4 JSON 序列化输出

组装好 ExportData 对象后,通过 JSON.stringify 序列化为格式化的 JSON 字符串。第三个参数 2 表示使用 2 个空格缩进,便于人类阅读。

const json = JSON.stringify(data, null, 2);
ToastUtil.show('数据已准备(JSON格式)');

序列化后的 JSON 输出示例如下:

{
  "bills": "[{\"id\":\"1\",\"money\":100,\"type\":\"expense\",\"remark\":\"午餐\",\"date\":\"2026-07-28\"}]",
  "categories": "[{\"id\":\"1\",\"name\":\"餐饮\",\"type\":\"expense\"}]",
  "exportTime": "2026-07-28T10:30:00.000Z"
}

三、SettingView 中的导出入口

3.1 导出数据列表项

数据导出功能作为 SettingView 设置列表的一个 ListItem,点击后触发 exportData 方法。列表项采用标准箭头布局,左侧标题、右侧箭头图标。

// 数据导出
ListItem() {
  Row() {
    Text('导出数据').fontSize(AppFontSize.MD).fontColor(AppColors.PrimaryText).layoutWeight(1)
    Image($r('app.media.icon_arrow_right')).width(20).height(20).fillColor(AppColors.SecondaryText)
  }
  .width('100%').height(56)
  .padding({ left: AppSpace.MD, right: AppSpace.MD })
  .backgroundColor(AppColors.CardBackground).borderRadius(AppSpace.CardRadius)
  .onClick(() => { this.exportData(); })
}

3.2 完整 SettingView 代码

以下是 SettingView 的完整实现,包含导出数据与清空数据两个核心功能。所有代码与实际源码完全一致。

// pages/SettingView.ets
import { AppColors } from '../theme/Colors';
import { AppFontSize } from '../theme/Typography';
import { AppSpace } from '../theme/Spacing';
import { RouterUtil } from '../utils/RouterUtil';
import { SettingRepository } from '../repository/SettingRepository';
import { PreferenceUtil } from '../utils/PreferenceUtil';
import { ToastUtil } from '../utils/ToastUtil';
import { ConfirmDialog } from '../components/dialog/ConfirmDialog';

@Entry
@Component
struct SettingView {
  @State darkMode: boolean = false;
  @State showClearConfirm: boolean = false;

  aboutToAppear(): void {
    this.loadSettings();
  }

  private async loadSettings(): Promise<void> {
    this.darkMode = await SettingRepository.getInstance().loadDarkMode();
  }

  build() {
    Column() {
      Row() {
        Image($r('app.media.icon_back')).width(24).height(24).fillColor(AppColors.PrimaryText)
          .onClick(() => { RouterUtil.back(); })
        Text('设置').fontSize(AppFontSize.XL).fontWeight(FontWeight.Bold).layoutWeight(1).textAlign(TextAlign.Center)
      }.width('100%').height(56).alignItems(VerticalAlign.Center)

      List({ space: AppSpace.SM }) {
        // 深色模式
        ListItem() {
          Row() {
            Text('深色模式').fontSize(AppFontSize.MD).fontColor(AppColors.PrimaryText).layoutWeight(1)
            Toggle({ type: ToggleType.Switch, isOn: this.darkMode })
              .onChange((isOn: boolean) => {
                this.darkMode = isOn;
                SettingRepository.getInstance().saveDarkMode(isOn);
                ToastUtil.show('重启应用后生效');
              })
          }
          .width('100%').height(56)
          .padding({ left: AppSpace.MD, right: AppSpace.MD })
          .backgroundColor(AppColors.CardBackground).borderRadius(AppSpace.CardRadius)
        }

        // 数据导出
        ListItem() {
          Row() {
            Text('导出数据').fontSize(AppFontSize.MD).fontColor(AppColors.PrimaryText).layoutWeight(1)
            Image($r('app.media.icon_arrow_right')).width(20).height(20).fillColor(AppColors.SecondaryText)
          }
          .width('100%').height(56)
          .padding({ left: AppSpace.MD, right: AppSpace.MD })
          .backgroundColor(AppColors.CardBackground).borderRadius(AppSpace.CardRadius)
          .onClick(() => { this.exportData(); })
        }

        // 清空数据
        ListItem() {
          Row() {
            Text('清空所有数据').fontSize(AppFontSize.MD).fontColor(AppColors.Expense).layoutWeight(1)
          }
          .width('100%').height(56)
          .padding({ left: AppSpace.MD, right: AppSpace.MD })
          .backgroundColor(AppColors.CardBackground).borderRadius(AppSpace.CardRadius)
          .onClick(() => { this.showClearConfirm = true; })
        }

        // 关于
        ListItem() {
          Row() {
            Text('关于').fontSize(AppFontSize.MD).fontColor(AppColors.PrimaryText).layoutWeight(1)
            Image($r('app.media.icon_arrow_right')).width(20).height(20).fillColor(AppColors.SecondaryText)
          }
          .width('100%').height(56)
          .padding({ left: AppSpace.MD, right: AppSpace.MD })
          .backgroundColor(AppColors.CardBackground).borderRadius(AppSpace.CardRadius)
          .onClick(() => { RouterUtil.push('pages/AboutView'); })
        }
      }
      .layoutWeight(1).margin({ top: AppSpace.MD })

      if (this.showClearConfirm) {
        ConfirmDialog({
          title: '确认清空',
          message: '清空后将删除所有账单、分类和预算数据,此操作不可恢复!',
          confirmText: '清空',
          confirmColor: AppColors.Expense,
          onConfirm: () => { this.doClear(); },
          onCancel: () => { this.showClearConfirm = false; }
        })
      }
    }
    .height('100%').padding({ left: AppSpace.XL, right: AppSpace.XL, top: AppSpace.MD })
    .backgroundColor(AppColors.Background)
  }

  private async exportData(): Promise<void> {
    const billsJson = await PreferenceUtil.getInstance().getString('bills', '[]');
    const categoriesJson = await PreferenceUtil.getInstance().getString('categories', '[]');
    interface ExportData {
      bills: string;
      categories: string;
      exportTime: string;
    }
    const data: ExportData = {
      bills: billsJson,
      categories: categoriesJson,
      exportTime: new Date().toISOString()
    };
    const json = JSON.stringify(data, null, 2);
    ToastUtil.show('数据已准备(JSON格式)');
  }

  private async doClear(): Promise<void> {
    await PreferenceUtil.getInstance().clear();
    this.showClearConfirm = false;
    ToastUtil.show('数据已清空');
  }
}

3.3 导出触发流程

导出流程从用户点击"导出数据"列表项开始,经过数据读取、接口组装、JSON 序列化三个步骤,最终通过 Toast 反馈结果:

  1. 用户点击导出列表项,触发 onClick 回调
  2. 调用 this.exportData() 私有方法
  3. PreferenceUtil.getString 异步读取 billscategories
  4. 组装为 ExportData 接口对象
  5. JSON.stringify 序列化为格式化 JSON
  6. ToastUtil.show 提示导出完成

四、数据清空与恢复

4.1 doClear 清空实现

doClear 方法是数据清空的核心实现,调用 PreferenceUtil.getInstance().clear() 清空所有偏好数据。清空完成后关闭确认对话框并提示用户。

// pages/SettingView.ets - doClear 方法
private async doClear(): Promise<void> {
  await PreferenceUtil.getInstance().clear();
  this.showClearConfirm = false;
  ToastUtil.show('数据已清空');
}

4.2 ConfirmDialog 二次确认

数据清空是不可恢复的高危操作,必须通过 ConfirmDialog 进行二次确认。在 SettingView 中,通过 @State showClearConfirm 控制对话框的显示与隐藏。

if (this.showClearConfirm) {
  ConfirmDialog({
    title: '确认清空',
    message: '清空后将删除所有账单、分类和预算数据,此操作不可恢复!',
    confirmText: '清空',
    confirmColor: AppColors.Expense,
    onConfirm: () => { this.doClear(); },
    onCancel: () => { this.showClearConfirm = false; }
  })
}

安全设计:确认按钮使用 AppColors.Expense 红色背景突出危险操作,文案明确告知"此操作不可恢复",最大程度避免误操作。

4.3 清空操作防护策略

防护层级 实现方式 说明
视觉警示 AppColors.Expense 红色文字 列表项标题使用红色
二次确认 ConfirmDialog 弹窗 必须点击"清空"按钮确认
文案明确 “此操作不可恢复” 明确告知后果
取消机制 onCancel 回调 点击"取消"或遮罩关闭

五、PreferenceUtil 存储机制

5.1 偏好存储初始化

PreferenceUtil 是数据导出与清空的底层支撑。它基于 @kit.ArkDatapreferences 模块,需要在应用启动时初始化。

// utils/PreferenceUtil.ets
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';

const PREF_NAME = 'harmonyledger';

export class PreferenceUtil {
  private pref: preferences.Preferences | null = null;
  private static instance: PreferenceUtil | null = null;

  static getInstance(): PreferenceUtil {
    if (PreferenceUtil.instance === null) {
      PreferenceUtil.instance = new PreferenceUtil();
    }
    return PreferenceUtil.instance;
  }

  async init(context: common.Context): Promise<void> {
    this.pref = await preferences.getPreferences(context, PREF_NAME);
  }
}

5.2 字符串读写方法

数据导出依赖 getString 方法读取已持久化的数据,数据清空依赖 clear 方法清除所有数据。

async setString(key: string, value: string): Promise<void> {
  if (this.pref === null) return;
  await this.pref.put(key, value);
  await this.pref.flush();
}

async getString(key: string, defaultValue: string = ''): Promise<string> {
  if (this.pref === null) return defaultValue;
  let result: ESObject = await this.pref.get(key, defaultValue);
  return '' + result;
}

5.3 对象序列化存储

PreferenceUtil 还提供了泛型对象存储方法 setObject<T>,用于将复杂对象序列化为 JSON 后存储。这在备份恢复场景中非常实用。

async setObject<T>(key: string, value: T): Promise<void> {
  if (this.pref === null) return;
  const json = JSON.stringify(value);
  await this.pref.put(key, json);
  await this.pref.flush();
}

async getObject(key: string, defaultValue: ESObject | null = null): Promise<ESObject | null> {
  if (this.pref === null) return defaultValue;
  let json: ESObject = await this.pref.get(key, '');
  let jsonStr: string = '' + json;
  if (jsonStr.length === 0) {
    return defaultValue;
  }
  try {
    return JSON.parse(jsonStr);
  } catch (e) {
    return defaultValue;
  }
}

5.4 clear 全量清空

clear 方法清除偏好存储中的所有数据,SettingView.doClear 正是调用此方法实现数据清空。

async clear(): Promise<void> {
  if (this.pref === null) return;
  await this.pref.clear();
  await this.pref.flush();
}

5.5 API 速查表

方法 参数 返回值 导出场景用途
getString key, defaultValue Promise<string> 读取 bills/categories 数据
setString key, value Promise<void> 写入导出数据
setObject<T> key, value: T Promise<void> 存储备份对象
getObject key, defaultValue Promise<ESObject | null> 读取备份对象
clear Promise<void> 清空所有数据

六、备份恢复扩展设计

6.1 备份数据结构

当前 exportData 方法导出的数据包含账单与分类。完整的备份还应包含预算与设置数据。以下是推荐的备份接口定义。

// 扩展设计:完整备份接口
interface BackupData {
  bills: string;
  categories: string;
  budgets: string;
  settings: string;
  version: string;
  backupTime: string;
}
字段 类型 说明
bills string 账单数据 JSON
categories string 分类数据 JSON
budgets string 预算数据 JSON
settings string 设置项 JSON
version string 应用版本号,用于兼容性校验
backupTime string 备份时间戳

6.2 本地备份方案

利用 PreferenceUtil.setObject 方法,可以将完整的备份数据序列化后存储到本地偏好中。

// 扩展设计:本地备份方法
private async createBackup(): Promise<void> {
  const billsJson = await PreferenceUtil.getInstance().getString('bills', '[]');
  const categoriesJson = await PreferenceUtil.getInstance().getString('categories', '[]');
  const budgetsJson = await PreferenceUtil.getInstance().getString('budgets', '[]');

  interface BackupData {
    bills: string;
    categories: string;
    budgets: string;
    version: string;
    backupTime: string;
  }

  const backup: BackupData = {
    bills: billsJson,
    categories: categoriesJson,
    budgets: budgetsJson,
    version: '1.0.0',
    backupTime: new Date().toISOString()
  };

  await PreferenceUtil.getInstance().setObject<BackupData>('backup_latest', backup);
  ToastUtil.show('备份成功');
}

6.3 恢复流程设计

恢复数据时,通过 PreferenceUtil.getObject 读取备份对象,解析后逐项写回偏好存储。恢复前应进行版本校验。

// 扩展设计:数据恢复方法
private async restoreBackup(): Promise<void> {
  const backup = await PreferenceUtil.getInstance().getObject('backup_latest', null);
  if (backup === null) {
    ToastUtil.show('暂无备份');
    return;
  }

  try {
    const bills: string = backup.bills;
    const categories: string = backup.categories;
    await PreferenceUtil.getInstance().setString('bills', bills);
    await PreferenceUtil.getInstance().setString('categories', categories);
    ToastUtil.show('恢复成功');
  } catch (e) {
    ToastUtil.show('恢复失败:数据格式错误');
  }
}

6.4 版本兼容处理

备份与恢复的核心挑战是版本兼容。当应用升级导致数据结构变更时,旧备份可能无法直接恢复。

// 扩展设计:版本校验
interface BackupData {
  bills: string;
  categories: string;
  budgets: string;
  version: string;
  backupTime: string;
}

private checkVersion(backup: BackupData, currentVersion: string): boolean {
  if (backup.version !== currentVersion) {
    console.warn('Backup version mismatch: ' + backup.version + ' vs ' + currentVersion);
    // 可在此处添加数据迁移逻辑
    return false;
  }
  return true;
}

七、导出格式详解

7.1 JSON 格式结构

当前项目采用 JSON 格式导出,JSON.stringify(data, null, 2) 的第三个参数 2 表示使用 2 个空格缩进。导出的 JSON 结构清晰、易于解析。

const json = JSON.stringify(data, null, 2);
{
  "bills": "[{\"id\":\"1\",\"money\":100,\"type\":\"expense\"}]",
  "categories": "[{\"id\":\"1\",\"name\":\"餐饮\"}]",
  "exportTime": "2026-07-28T10:30:00.000Z"
}

注意billscategories 字段的值是 JSON 字符串(字符串内嵌套 JSON),而非直接的数组。这是因为 PreferenceUtil 存储时已将数组序列化为字符串。

7.2 CSV 格式对比

若需支持 CSV 导出,可将账单数据转换为逗号分隔的表格格式。但 CSV 无法表达嵌套结构,适合简单报表场景。

// 扩展设计:CSV 导出(仅账单)
private exportBillsToCSV(billsJson: string): string {
  const bills: Array<ESObject> = JSON.parse(billsJson);
  const header: string = 'id,money,type,remark,date';
  const rows: string = bills.map((b: ESObject) => {
    return b.id + ',' + b.money + ',' + b.type + ',' + b.remark + ',' + b.date;
  }).join('\n');
  return header + '\n' + rows;
}

7.3 格式选择建议

维度 JSON CSV
结构完整性 保留嵌套结构 扁平化
类型保留 保留原始类型 全部转字符串
可读性 需格式化工具 Excel 直接打开
恢复能力 可完整恢复 仅可恢复账单
体积 较大 较小

建议:日常备份使用 JSON 格式,财务报表导出可扩展 CSV 格式。


八、ArkTS 类型安全规范

8.1 禁用 any 类型

ArkTS 严格禁止使用 any 类型。在数据导出场景中,所有变量都有明确的类型标注,包括 stringbooleanPromise<void> 等。违反此规范会导致编译错误

8.2 interface 定义规范

数据导出的核心是 ExportData 接口。通过 interface 定义数据结构,可以确保编译期类型检查,避免运行时错误。

错误写法(禁止)

// 违反 ArkTS 类型安全,禁止使用 any
private async exportData(): Promise<void> {
  const data: any = {
    bills: '',
    categories: '',
    exportTime: ''
  };
  const json = JSON.stringify(data);
}

正确写法(推荐)

// 使用 interface 定义明确的类型约束
interface ExportData {
  bills: string;
  categories: string;
  exportTime: string;
}

const data: ExportData = {
  bills: billsJson,
  categories: categoriesJson,
  exportTime: new Date().toISOString()
};

8.3 类型规范对比

场景 错误写法 正确写法
导出数据类型 data: any data: ExportData
仓库类名 中文类名(编译报错) SettingRepository
页面结构体名 中文结构体名(编译报错) SettingView
列表数据类型 data: Array<any> data: Array<ExportData>
ForEach 键值函数 (item: any) => item.id (item: ExportData) => item.exportTime
方法参数类型 save(item: any) setObject<T>(key: string, value: T)

8.4 异步方法返回类型

exportDatadoClear 都是异步方法,返回类型必须显式标注为 Promise<void>

private async exportData(): Promise<void> { ... }
private async doClear(): Promise<void> { ... }

九、运行验证

9.1 构建命令

hvigorw assembleHap --mode module -p product=default

9.2 验证清单

验证项 操作步骤 预期结果
导出空数据 无账单时点击"导出数据" Toast 提示"数据已准备(JSON格式)"
导出有数据 有账单时点击"导出数据" Toast 提示"数据已准备(JSON格式)"
JSON 格式正确 检查 json 变量内容 包含 bills、categories、exportTime 三字段
清空确认弹窗 点击"清空所有数据" 弹出 ConfirmDialog 确认框
取消清空 在确认框点击"取消" 对话框关闭,数据不变
确认清空 在确认框点击"清空" 数据清空,Toast 提示"数据已清空"
清空后验证 清空后查看账单页 账单列表为空
默认值生效 无 bills 数据时导出 billsJson'[]'

验证要点:导出功能需检查 JSON 内容是否正确;清空功能需确认数据确实被清除。


十、常见问题

10.1 导出数据为空

现象:导出后 JSON 中 bills 字段为 '[]',但页面上有账单数据。

原因PreferenceUtil 未初始化,getString 始终返回默认值 '[]'

解决:在 EntryAbility.onCreate 中调用 PreferenceUtil.getInstance().init(this.context) 完成初始化。

10.2 清空后数据仍在

现象:点击清空并确认后,重新进入账单页数据仍然存在。

原因PreferenceUtil.prefnullclear 方法直接 return 未执行。

解决:确保 PreferenceUtil.init 已在应用启动时调用。

10.3 JSON 序列化失败

现象JSON.stringify 抛出异常。

原因ExportData 对象中包含循环引用或 undefined 值。

解决:确保所有字段值都是基本类型(stringnumberboolean),不包含函数或循环引用对象。

10.4 恢复数据格式错误

现象:恢复备份时提示"数据格式错误"。

原因:备份 JSON 结构与当前版本不匹配,或备份文件被篡改。

解决:在恢复前进行版本校验与数据结构校验,参考第六章的版本兼容处理方案。


十一、Git 提交

11.1 提交命令

git add .
git commit -m "feat(data): 数据导出与清空

- 实现 SettingView.exportData 数据导出(JSON格式)
- 实现 SettingView.doClear 数据清空(二次确认)
- 定义 ExportData 接口约束导出结构
- 基于 PreferenceUtil 完成数据读写
- 扩展设计备份恢复方案"

11.2 变更记录

## [v0.2.5] - 2026-07-28
### Added
- SettingView.exportData 数据导出方法
- SettingView.doClear 数据清空方法
- ExportData 接口定义
### Changed
- 完善设置中心数据管理能力

附录:运行效果截图

在这里插入图片描述


总结

本文完整介绍了 数据导出、备份与恢复 的实现方案,基于 SettingView 中的 exportDatadoClear 方法展开,深入解析了 PreferenceUtil 存储机制,并扩展设计了本地备份与恢复的完整方案。通过本篇你可以:

  • 实现 JSON 格式的数据导出,使用 ExportData 接口约束结构
  • 掌握 PreferenceUtil 偏好存储的读写与清空机制
  • 封装 ConfirmDialog 二次确认保护高危清空操作
  • 扩展设计本地备份与数据恢复的完整链路
  • 遵循 ArkTS 类型安全规范,使用 interface 代替 any 类型

下一篇预告:第 26 篇将进入应用性能优化与体验打磨阶段,涵盖列表懒加载、防抖节流、骨架屏等企业级优化实践。


如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!欢迎在评论区留言讨论,或参与下方投票,告诉我你最关注记账应用的哪个数据管理能力?


相关资源

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐