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

OpenDroneMap (ODM) 无人机影像三维模型重建安装及使用快速上手

OpenDroneMap (ODM) 无人机影像三维模型重建安装及使用快速上手

1 文档概述 本文档是指导用户从零开始,使用 OpenDroneMap 对无人机采集的影像数据进行处理,生成三维点云、数字表面模型(DSM)、正射影像图(Orthomosaic)等成果。 本文档的预期读者为拥有无人机航拍影像(JPG/PNG格式)并希望进行三维建模的用户。 2.1 系统运行环境要求 - 操作系统:Windows 10/11, macOS, 或 Linux (推荐 Ubuntu)。 - CPU:多核心处理器(4核以上推荐,8核或更多更佳)(处理200张以上影像建议16GB+)。 - 内存 (RAM):至少 16GB,处理大面积区域建议 32GB 或以上。 - 硬盘空间:预留充足的存储空间。原始影像、中间文件和最终成果会占用大量空间。建议准备 影像大小的10-20倍

By Ne0inhk
【无人机】无人机路径规划算法

【无人机】无人机路径规划算法

目录 一、引言:无人机与路径规划算法 二、路径规划算法基础 (一)定义与重要性 (二)规划目标与约束条件 三、常见路径规划算法详解 (一)A * 算法 (二)Dijkstra 算法 (三)RRT(快速扩展随机树)算法 (四)蚁群算法 四、算法应用实例与效果展示 (一)不同场景下的算法应用 (二)算法性能对比数据 五、算法的优化与发展趋势 (一)现有算法的优化策略 (二)结合新技术的发展方向 六、挑战与展望 (一)面临的技术挑战 (二)未来应用前景 七、结论 一、引言:无人机与路径规划算法 在科技飞速发展的今天,无人机作为一种极具创新性的技术产物,已深度融入我们生活的方方面面,

By Ne0inhk
【PX4+ROS完全指南】从零实现无人机Offboard控制:模式解析与实战

【PX4+ROS完全指南】从零实现无人机Offboard控制:模式解析与实战

引言 无人机自主飞行是机器人领域的热门方向,而PX4作为功能强大的开源飞控,配合ROS(机器人操作系统)的灵活性与生态,成为实现高级自主飞行的黄金组合。然而,许多初学者对PX4的飞行模式理解不清,更不知道如何通过ROS编写可靠的Offboard控制程序。 本文将带你彻底搞懂PX4 6大核心飞行模式,实现无人机的自动起飞、悬停、轨迹跟踪(圆形/方形/螺旋)与降落。 亮点一览: * ✅ 深度解析PX4飞行模式(稳定/定高/位置/自动/Offboard) * ✅ 明确ROS可控制的模式与指令接口 * ✅ 完整的ROS功能包(C++实现,状态机设计) * ✅ 支持位置控制与速度控制双模式 * ✅ 内置圆形、方形、螺旋轨迹生成器 * ✅ 详细的安全机制与失效保护配置 无论你是准备参加比赛、做科研,还是想入门无人机开发,这篇文章都将是你宝贵的参考资料。 第一部分:PX4飞行模式深度剖析 PX4的飞行模式可以看作一个控制权逐级递增的层级结构。理解这些模式是编写控制程序的前提。 1. 稳定模式(STABILIZED / MANUAL / ACRO) * 核心特点:

By Ne0inhk

Vivado完整license文件获取与配置指南

本文还有配套的精品资源,点击获取 简介:Vivado是由Xilinx开发的FPGA和SoC设计综合工具,支持Verilog、VHDL等硬件描述语言,提供高级综合、仿真、IP集成等功能。本资源包“Vivado_的license文件.zip”包含用于解锁Vivado完整功能的许可证文件。介绍了许可证服务器配置、.lic文件管理、浮动与固定许可证区别、激活流程、更新与诊断等核心内容。适用于FPGA开发者、嵌入式系统工程师及学习者,帮助其合法配置Vivado环境,提升开发效率和项目执行能力。 1. Vivado工具与FPGA开发环境概述 Xilinx Vivado设计套件是面向FPGA和SoC开发的集成化软件平台,广泛应用于通信、工业控制、人工智能、嵌入式视觉等多个高科技领域。其核心功能包括项目创建、综合、实现、仿真、调试及系统级集成,支持从设计输入到硬件验证的全流程开发。 Vivado不仅提供了图形化界面(GUI)便于初学者快速上手,还支持Tcl脚本自动化操作,满足高级用户的大规模工程管理需求。其模块化架构设计使得开发者可以灵活选择所需功能组件,如HLS(高层次综合)、IP In

By Ne0inhk