HarmonyOS APP<玩转React>开源教程五:项目架构设计

HarmonyOS APP<玩转React>开源教程五:项目架构设计

第5次:项目架构设计

良好的项目架构是应用可维护性和可扩展性的基础。本次课程将学习分层架构设计原则,规划项目目录结构,并搭建完整的项目骨架。

学习目标

  • 理解分层架构设计原则
  • 掌握项目目录结构规划
  • 学会常量和配置管理
  • 完成项目骨架搭建

5.1 分层架构设计原则

为什么需要分层?

随着项目规模增长,如果所有代码都写在一起:

  • 代码难以维护
  • 功能难以复用
  • 团队协作困难
  • 测试难以进行

分层架构的优势

┌─────────────────────────────────────┐ │ 表现层 (Pages) │ ← 用户界面 ├─────────────────────────────────────┤ │ 组件层 (Components) │ ← 可复用 UI ├─────────────────────────────────────┤ │ 服务层 (Services) │ ← 业务逻辑 ├─────────────────────────────────────┤ │ 数据层 (Data) │ ← 数据管理 ├─────────────────────────────────────┤ │ 模型层 (Models) │ ← 类型定义 └─────────────────────────────────────┘ 

各层职责

层级职责示例
Pages页面组件,处理路由Index.ets, LessonDetail.ets
Components可复用 UI 组件ModuleCard.ets, CodeBlock.ets
Services业务逻辑封装TutorialService.ets, ProgressService.ets
Data数据定义和管理TutorialData.ets, SourceCodeData.ets
Models类型和接口定义Models.ets
Common公共工具和常量Constants.ets, StorageUtil.ets

依赖原则

Pages → Components → Services → Data → Models ↓ ↓ ↓ ↓ ↓ └─────────┴───────────┴─────────┴───────┘ Common 
  • 上层可以依赖下层
  • 下层不能依赖上层
  • 同层之间尽量避免依赖

5.2 目录结构规划

完整目录结构

entry/src/main/ets/ ├── common/ # 公共模块 │ ├── Constants.ets # 常量定义 │ ├── StorageUtil.ets # 存储工具 │ └── ThemeUtil.ets # 主题工具 │ ├── components/ # 可复用组件 │ ├── CodeBlock.ets # 代码块组件 │ ├── FloatingButton.ets # 浮动按钮 │ ├── HeroBanner.ets # 首页横幅 │ ├── KnowledgeCard.ets # 知识卡片 │ ├── LessonItem.ets # 课程项 │ ├── ModuleCard.ets # 模块卡片 │ ├── ProgressRing.ets # 进度环 │ ├── QuizOptionItem.ets # 测验选项 │ ├── QuizQuestion.ets # 测验题目 │ ├── QuizResultCard.ets # 测验结果 │ └── SkillTreeNode.ets # 技能树节点 │ ├── data/ # 数据定义 │ ├── TutorialData.ets # 教程数据 │ ├── SourceCodeData.ets # 源码数据 │ ├── OpenSourceProjectData.ets # 开源项目数据 │ ├── InterviewQuizData.ets # 面试题数据 │ └── DemoProjectData.ets # 示例项目数据 │ ├── models/ # 数据模型 │ └── Models.ets # 类型定义 │ ├── pages/ # 页面组件 │ ├── Index.ets # 首页 │ ├── ModuleDetail.ets # 模块详情 │ ├── LessonDetail.ets # 课程详情 │ ├── QuizPage.ets # 测验页 │ ├── QuizBankPage.ets # 题库页 │ ├── CodePlayground.ets # 代码调试 │ ├── SearchPage.ets # 搜索页 │ ├── BookmarkPage.ets # 收藏页 │ ├── SourceCodePage.ets # 源码学习 │ ├── OpenSourcePage.ets # 开源项目 │ └── WrongAnswerBookPage.ets # 错题本 │ ├── services/ # 业务服务 │ ├── TutorialService.ets # 教程服务 │ ├── ProgressService.ets # 进度服务 │ ├── QuizService.ets # 测验服务 │ ├── BookmarkService.ets # 收藏服务 │ ├── SearchService.ets # 搜索服务 │ ├── BadgeService.ets # 徽章服务 │ └── WrongAnswerService.ets # 错题服务 │ └── entryability/ └── EntryAbility.ets # 应用入口 

命名规范

类型命名规则示例
页面PascalCaseIndex.ets, ModuleDetail.ets
组件PascalCaseModuleCard.ets, CodeBlock.ets
服务PascalCase + ServiceTutorialService.ets
工具PascalCase + UtilStorageUtil.ets
常量UPPER_SNAKE_CASEAPP_NAME, PRIMARY_COLOR
接口PascalCaseLearningModule, UserProgress

5.3 常量管理:Constants.ets

创建常量文件

entry/src/main/ets/common/ 下创建 Constants.ets

/** * 应用常量定义 * 集中管理所有常量,便于维护和修改 *//** * 应用基本信息 */exportclassAppConstants{staticreadonlyAPP_NAME:string='React 学习教程';staticreadonlyAPP_VERSION:string='1.0.0';staticreadonlyPREFERENCES_NAME:string='react_tutorial_prefs';}/** * React 品牌色 */exportclassReactColors{staticreadonlyPRIMARY:string='#61DAFB';staticreadonlyPRIMARY_DARK:string='#20232a';staticreadonlySECONDARY_DARK:string='#282c34';staticreadonlyGRADIENT_START:string='#61DAFB';staticreadonlyGRADIENT_END:string='#21a0c4';}/** * 浅色主题颜色 */exportclassLightThemeColors{staticreadonlyBACKGROUND:string='#f8f9fa';staticreadonlyCARD_BACKGROUND:string='#ffffff';staticreadonlyTEXT_PRIMARY:string='#1a1a2e';staticreadonlyTEXT_SECONDARY:string='#495057';staticreadonlyDIVIDER:string='#e9ecef';}/** * 深色主题颜色 */exportclassDarkThemeColors{staticreadonlyBACKGROUND:string='#1a1a2e';staticreadonlyCARD_BACKGROUND:string='#282c34';staticreadonlyTEXT_PRIMARY:string='#ffffff';staticreadonlyTEXT_SECONDARY:string='#d1d5db';staticreadonlyDIVIDER:string='#3d3d5c';}/** * 难度等级颜色 */exportclassDifficultyColors{staticreadonlyBEGINNER:string='#51cf66';staticreadonlyBASIC:string='#339af0';staticreadonlyINTERMEDIATE:string='#ff922b';staticreadonlyADVANCED:string='#ff6b6b';staticreadonlyECOSYSTEM:string='#9775fa';}/** * 存储键名 */exportclassStorageKeys{staticreadonlyUSER_PROGRESS:string='user_progress';staticreadonlyBOOKMARKS:string='bookmarks';staticreadonlyTHEME_MODE:string='theme_mode';staticreadonlyQUIZ_HISTORY:string='quiz_history';staticreadonlyWRONG_ANSWERS:string='wrong_answers';staticreadonlyQUIZ_STATISTICS:string='quiz_statistics';}/** * 路由路径 */exportclassRoutePaths{staticreadonlyINDEX:string='pages/Index';staticreadonlyMODULE_DETAIL:string='pages/ModuleDetail';staticreadonlyLESSON_DETAIL:string='pages/LessonDetail';staticreadonlyQUIZ_PAGE:string='pages/QuizPage';staticreadonlyQUIZ_BANK:string='pages/QuizBankPage';staticreadonlyCODE_PLAYGROUND:string='pages/CodePlayground';staticreadonlySEARCH:string='pages/SearchPage';staticreadonlyBOOKMARK:string='pages/BookmarkPage';}/** * 难度等级类型 */exporttypeDifficultyLevel='beginner'|'basic'|'intermediate'|'advanced'|'ecosystem';/** * 难度等级显示名称 */exportconst DifficultyNames: Record<DifficultyLevel,string>={'beginner':'入门','basic':'基础','intermediate':'进阶','advanced':'高级','ecosystem':'生态'};/** * 获取难度等级颜色 */exportfunctiongetDifficultyColor(difficulty: DifficultyLevel):string{const colors: Record<DifficultyLevel,string>={'beginner': DifficultyColors.BEGINNER,'basic': DifficultyColors.BASIC,'intermediate': DifficultyColors.INTERMEDIATE,'advanced': DifficultyColors.ADVANCED,'ecosystem': DifficultyColors.ECOSYSTEM};return colors[difficulty]?? DifficultyColors.BEGINNER;}/** * 应用配置 */exportclassAppConfig{staticreadonlyQUIZ_PASSING_SCORE:number=60;staticreadonlySTREAK_BADGE_DAYS:number=7;staticreadonlyMAX_WRONG_ANSWERS:number=100;staticreadonlySEARCH_DEBOUNCE_MS:number=300;}

使用常量

import{ AppConstants, ReactColors, StorageKeys, getDifficultyColor }from'../common/Constants';// 使用应用信息Text(AppConstants.APP_NAME)// 使用颜色.backgroundColor(ReactColors.PRIMARY)// 使用存储键 StorageUtil.getObject(StorageKeys.USER_PROGRESS, defaultValue)// 使用函数let color =getDifficultyColor('intermediate');

5.4 实操:搭建完整项目骨架

现在,让我们创建项目的完整目录结构和基础文件。

步骤 1:创建目录结构

entry/src/main/ets/ 下创建以下目录:

  • common/
  • components/
  • data/
  • models/
  • services/

步骤 2:创建 StorageUtil.ets

common/ 下创建存储工具:

/** * 持久化存储工具类 */import{ preferences }from'@kit.ArkData';import{ AppConstants }from'./Constants';exportclassStorageUtil{privatestatic preferencesInstance: preferences.Preferences |null=null;/** * 初始化 */staticasyncinit(context: Context):Promise<void>{try{ StorageUtil.preferencesInstance =await preferences.getPreferences( context, AppConstants.PREFERENCES_NAME);console.info('[StorageUtil] Initialized');}catch(error){console.error('[StorageUtil] Init failed:', error);}}/** * 获取 Preferences 实例 */privatestaticgetPreferences(): preferences.Preferences {if(!StorageUtil.preferencesInstance){thrownewError('StorageUtil not initialized');}return StorageUtil.preferencesInstance;}/** * 获取字符串 */staticasyncgetString(key:string, defaultValue:string=''):Promise<string>{try{const prefs = StorageUtil.getPreferences();returnawait prefs.get(key, defaultValue)asstring;}catch(error){console.error(`[StorageUtil] getString failed for ${key}:`, error);return defaultValue;}}/** * 设置字符串 */staticasyncsetString(key:string, value:string):Promise<void>{try{const prefs = StorageUtil.getPreferences();await prefs.put(key, value);await prefs.flush();}catch(error){console.error(`[StorageUtil] setString failed for ${key}:`, error);}}/** * 获取对象 */staticasyncgetObject<T>(key:string, defaultValue:T):Promise<T>{try{const prefs = StorageUtil.getPreferences();const jsonStr =await prefs.get(key,'')asstring;if(!jsonStr)return defaultValue;returnJSON.parse(jsonStr)asT;}catch(error){console.error(`[StorageUtil] getObject failed for ${key}:`, error);return defaultValue;}}/** * 设置对象 */staticasyncsetObject<T>(key:string, value:T):Promise<void>{try{const prefs = StorageUtil.getPreferences();await prefs.put(key,JSON.stringify(value));await prefs.flush();}catch(error){console.error(`[StorageUtil] setObject failed for ${key}:`, error);}}/** * 删除键 */staticasyncremove(key:string):Promise<void>{try{const prefs = StorageUtil.getPreferences();await prefs.delete(key);await prefs.flush();}catch(error){console.error(`[StorageUtil] remove failed for ${key}:`, error);}}/** * 清空所有数据 */staticasyncclear():Promise<void>{try{const prefs = StorageUtil.getPreferences();await prefs.clear();await prefs.flush();}catch(error){console.error('[StorageUtil] clear failed:', error);}}}

步骤 3:更新 ThemeUtil.ets

/** * 主题工具类 */import{ LightThemeColors, DarkThemeColors }from'./Constants';exportenum ThemeMode {AUTO='auto',LIGHT='light',DARK='dark'}exportinterfaceThemeColors{ background:string; cardBackground:string; textPrimary:string; textSecondary:string; divider:string;}exportconst LightTheme: ThemeColors ={ background: LightThemeColors.BACKGROUND, cardBackground: LightThemeColors.CARD_BACKGROUND, textPrimary: LightThemeColors.TEXT_PRIMARY, textSecondary: LightThemeColors.TEXT_SECONDARY, divider: LightThemeColors.DIVIDER};exportconst DarkTheme: ThemeColors ={ background: DarkThemeColors.BACKGROUND, cardBackground: DarkThemeColors.CARD_BACKGROUND, textPrimary: DarkThemeColors.TEXT_PRIMARY, textSecondary: DarkThemeColors.TEXT_SECONDARY, divider: DarkThemeColors.DIVIDER};exportfunctioninitTheme(context: Context):void{ AppStorage.setOrCreate('isDarkMode',false); AppStorage.setOrCreate('themeMode', ThemeMode.LIGHT);}exportfunctiontoggleTheme():void{const isDark = AppStorage.get<boolean>('isDarkMode')??false; AppStorage.set('isDarkMode',!isDark); AppStorage.set('themeMode',!isDark ? ThemeMode.DARK: ThemeMode.LIGHT);}exportfunctiongetThemeColors(isDarkMode:boolean): ThemeColors {return isDarkMode ? DarkTheme : LightTheme;}

步骤 4:创建服务层基础文件

创建 services/TutorialService.ets

/** * 教程数据服务 */import{ LearningModule, Lesson, DifficultyType }from'../models/Models';exportclassTutorialService{privatestatic initialized:boolean=false;privatestatic modules: LearningModule[]=[];/** * 初始化服务 */staticinit():void{if(TutorialService.initialized)return;// 后续会加载实际数据 TutorialService.initialized =true;console.info('[TutorialService] Initialized');}/** * 获取所有模块 */staticgetAllModules(): LearningModule[]{return TutorialService.modules;}/** * 根据 ID 获取模块 */staticgetModuleById(id:string): LearningModule |undefined{return TutorialService.modules.find(m => m.id === id);}/** * 根据难度获取模块 */staticgetModulesByDifficulty(difficulty: DifficultyType): LearningModule[]{return TutorialService.modules.filter(m => m.difficulty === difficulty);}/** * 获取总课程数 */staticgetTotalLessonCount():number{return TutorialService.modules.reduce((sum, m)=> sum + m.lessons.length,0);}}

创建 services/ProgressService.ets

/** * 进度管理服务 */import{ StorageUtil }from'../common/StorageUtil';import{ StorageKeys }from'../common/Constants';import{ UserProgress,DEFAULT_USER_PROGRESS, LearningModule }from'../models/Models';exportclassProgressService{privatestatic cachedProgress: UserProgress |null=null;/** * 加载用户进度 */staticasyncloadProgress():Promise<UserProgress>{try{const progress =await StorageUtil.getObject<UserProgress>( StorageKeys.USER_PROGRESS,DEFAULT_USER_PROGRESS); ProgressService.cachedProgress = progress;return progress;}catch(error){console.error('[ProgressService] Load failed:', error);returnDEFAULT_USER_PROGRESS;}}/** * 保存用户进度 */staticasyncsaveProgress(progress: UserProgress):Promise<void>{try{await StorageUtil.setObject(StorageKeys.USER_PROGRESS, progress); ProgressService.cachedProgress = progress;}catch(error){console.error('[ProgressService] Save failed:', error);}}/** * 标记课程完成 */staticasyncmarkLessonComplete(lessonId:string):Promise<void>{const progress =await ProgressService.loadProgress();if(!progress.completedLessons.includes(lessonId)){ progress.completedLessons.push(lessonId); progress.currentLesson = lessonId;await ProgressService.saveProgress(progress);}}/** * 计算模块完成百分比 */staticgetCompletionPercentage(module: LearningModule, progress: UserProgress):number{if(module.lessons.length ===0)return0;const completed = module.lessons.filter( l => progress.completedLessons.includes(l.id)).length;return Math.round((completed / module.lessons.length)*100);}/** * 更新连续学习天数 */staticasyncupdateStreak():Promise<number>{const progress =await ProgressService.loadProgress();const today =newDate().toISOString().split('T')[0];if(progress.lastStudyDate === today){return progress.learningStreak;}const lastDate = progress.lastStudyDate;if(!lastDate){ progress.learningStreak =1;}else{const diff = Math.floor((newDate(today).getTime()-newDate(lastDate).getTime())/(1000*60*60*24)); progress.learningStreak = diff ===1? progress.learningStreak +1:1;} progress.lastStudyDate = today;await ProgressService.saveProgress(progress);return progress.learningStreak;}}

步骤 5:更新页面路由配置

更新 entry/src/main/resources/base/profile/main_pages.json

{"src":["pages/Index","pages/ModuleDetail","pages/LessonDetail","pages/QuizPage","pages/QuizBankPage","pages/CodePlayground","pages/SearchPage","pages/BookmarkPage","pages/SourceCodePage","pages/OpenSourcePage"]}

项目结构验证

完成后,你的项目结构应该如下:

entry/src/main/ets/ ├── common/ │ ├── Constants.ets ✓ │ ├── StorageUtil.ets ✓ │ └── ThemeUtil.ets ✓ ├── components/ (待创建) ├── data/ (待创建) ├── models/ │ └── Models.ets ✓ ├── pages/ │ └── Index.ets ✓ ├── services/ │ ├── TutorialService.ets ✓ │ └── ProgressService.ets ✓ └── entryability/ └── EntryAbility.ets ✓ 

本次课程小结

通过本次课程,你已经:

✅ 理解了分层架构设计原则
✅ 掌握了项目目录结构规划
✅ 学会了常量和配置的集中管理
✅ 创建了存储工具类
✅ 搭建了服务层基础框架
✅ 完成了项目骨架搭建


课后练习

  1. 添加日志工具:创建 LogUtil.ets,封装日志输出方法
  2. 扩展常量:添加更多应用配置常量
  3. 创建更多服务:按照模板创建 BookmarkService.ets

下次预告

第6次:数据模型设计与实现

我们将深入设计应用的数据模型:

  • 学习模块数据结构
  • 课程内容数据结构
  • 用户进度数据结构
  • 测验相关数据结构

完善的数据模型是应用功能的基础!

Read more

从DeepSeek-R1爆火看开源大模型推理优化:我在脉脉找到的实战方案

从DeepSeek-R1爆火看开源大模型推理优化:我在脉脉找到的实战方案

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、场景痛点直击:两个行业的共性困境与差异化难题 * 1. 电商智能客服场景(日均请求10万+) * 2. 金融智能咨询场景(日均请求3万+) * 二、实战突破:分场景落地优化方案(附完整代码+流程图) * 1. 核心优化架构总览(流程图) * 2. 分场景核心代码实现(新增4个关键代码片段) * (1)量化分级实现(适配金融场景精度需求) * (2)多租户隔离与共享实例实现(适配电商、金融双场景) * (3)边缘节点轻量化部署代码(适配电商峰值卸载) * (4)动态批处理与负载调度优化(核心优化代码) * 3. 优化效果对比表(分场景) * 三、脉向AI核心价值:技术人破圈的“

By Ne0inhk
基于ESP32_CAM与Qt Creator的智能视频监控项目(代码开源)

基于ESP32_CAM与Qt Creator的智能视频监控项目(代码开源)

前言:本文为手把手教学的基于 ESP32_CAM 与 Qt Creator 的智能视频监控项目,项目使用的 MCU 为乐鑫的 ESP32_CAM 搭配 Qt Creator 制作上位机,Qt 的版本为 Qt 5.9.0。本项目的智能 ESP32 Camera 拥有多种视频格式解码、WIFI 灯源控制、WIFI Camera 和智能预警等功能。项目分为上位机与下位机两部分的代码编程,也包含简单的图像算法设计,算是一个 ESP32 很好的练手项目。希望这篇博文能给读者朋友的工程项目给予些许帮助,Respect(代码开源)! 硬件与软件:ESP32_CAM、iKun ESP32 Camera Studio、Arduino IDE、Qt

By Ne0inhk
Flutter 三方库 git_hooks 鸿蒙强干预研发质量审核截断防线设防适配解析:依托钩子拦截引擎封锁全域代码递交链路建立极强合规化审计审查防火墙斩断-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 git_hooks 鸿蒙强干预研发质量审核截断防线设防适配解析:依托钩子拦截引擎封锁全域代码递交链路建立极强合规化审计审查防火墙斩断-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 git_hooks 鸿蒙强干预研发质量审核截断防线设防适配解析:依托钩子拦截引擎封锁全域代码递交链路建立极强合规化审计审查防火墙斩断技术债堆砌 前言 在 OpenHarmony 的大规模团队协作中,代码质量是团队的生命线。如果没有有效的约束,不符合规范的代码(甚至是无法通过静态分析的代码)会轻易地通过 git commit 进入代码库,导致 CI 构建频繁失败。git_hooks 库为 Flutter 开发者提供了一种轻量级的脚本化方案,可以在 Git 的关键生命周期(如提交前、推送前)自动运行检查。本文将带大家在鸿蒙端实战适配该库,夯实自动化工程的地基。 一、原直线性 / 概念介绍 1.1 基础原理/概念介绍 git_hooks 的核心逻辑是基于 Git

By Ne0inhk
GitHub 36k+ Star!Clawdbot 全方位保姆级教程:把你的 Mac 变成 24 小时在线的“贾维斯”

GitHub 36k+ Star!Clawdbot 全方位保姆级教程:把你的 Mac 变成 24 小时在线的“贾维斯”

🔥 GitHub 36k+ Star!Clawdbot 全方位保姆级教程:把你的 Mac 变成 24 小时在线的“贾维斯” 最近有一个 AI 项目在 GitHub 上杀疯了,甚至直接导致二手 Mac Mini 涨价。它就是 Clawdbot。 很多粉丝问我:“现在的 AI 不都是网页版的吗?这个有什么不一样?” 如果你还在用网页版的 ChatGPT 或 Claude,那你还在“用”AI;而 Clawdbot 让你**“拥有”**一个 AI 员工。 今天这篇保姆级教程,带你从零开始部署一只属于自己的 Clawdbot,让 AI 主动在 Telegram/微信/Discord

By Ne0inhk