当前位置: 首页 > news >正文

HarmonyOS Next 项目完整学习指南

HarmonyOS Next 项目完整学习指南

基于 HealthyMate(泰科健康)项目的实战学习文档

适合人群: 完成 HarmonyOS Next 基础学习的初学者

学习目标: 通过真实项目巩固基础知识,掌握实际开发技能


📚 目录

  • 第一章:项目概览
  • 第二章:核心概念速览
  • 第三章:项目结构详解
  • 第四章:从零开始 - 启动流程
  • 第五章:UI组件开发
  • 第六章:状态管理
  • 第七章:路由导航
  • 第八章:数据模型
  • 第九章:图表组件
  • 第十章:最佳实践
  • 第十一章:常见问题

第一章:项目概览

1.1 项目简介

**HealthyMate(泰科健康)**是一款基于 HarmonyOS Next 开发的综合性健康管理应用。

核心功能模块:

  • 🏠 首页模块: 健康检测、医院查询、热点资讯
  • 🔍 健康发现: 健康知识、视频播放、管理工具
  • 📊 健康数据: 数据展示、趋势分析、图表可视化
  • 👤 个人中心: 用户信息、设置管理

技术栈:

开发框架: HarmonyOS Next
开发语言: ArkTS (TypeScript 扩展)
UI框架: ArkUI (声明式UI)
开发工具: DevEco Studio
包管理: ohpm

1.2 学习路线图

启动流程 → UI组件 → 状态管理 → 路由导航 → 数据交互 → 高级特性↓         ↓         ↓          ↓          ↓         ↓Entry    @Component  @State    Navigation   Model    第三方库Ability   @Builder   AppStorage  router     Class    mpchart

第二章:核心概念速览

2.1 ArkTS 基础概念

2.1.1 装饰器系统

ArkTS 使用装饰器来增强类、组件和变量的功能:

装饰器作用使用场景示例
@Entry标记页面入口可独立运行的页面启动页、登录页
@Component定义自定义组件可复用的UI组件卡片、按钮
@State状态变量组件内部状态计数器、开关
@Prop单向数据传递父组件→子组件配置参数
@Link双向数据绑定父子组件共享表单数据
@Builder自定义构建函数动态UI内容插槽内容
@BuilderParam插槽参数接收UI内容卡片插槽
@Extend扩展组件样式统一样式文本样式
@Preview预览组件开发调试组件预览
2.1.2 核心组件

容器组件:

Column()    // 垂直布局
Row()       // 水平布局
Stack()     // 堆叠布局
Flex()      // 弹性布局
Grid()      // 网格布局
List()      // 列表布局
Swiper()    // 轮播容器
Tabs()      // 标签页容器

基础组件:

Text()      // 文本
Image()     // 图片
Button()    // 按钮
TextInput() // 文本输入

2.2 项目架构概念

2.2.1 应用生命周期
应用启动↓
EntryAbility.onCreate()     // Ability 创建↓
onWindowStageCreate()       // 窗口创建↓
loadContent('pages/SplashPage')  // 加载启动页↓
页面显示↓
aboutToAppear()            // 页面即将显示↓
build()                    // 构建UI↓
onPageShow()              // 页面已显示
2.2.2 组件生命周期
@Component
struct MyComponent {// 1. 组件即将创建aboutToAppear() {console.log('组件即将出现')// 初始化数据、启动定时器}// 2. 构建UI(每次状态变化都会触发)build() {Column() {Text('Hello')}}// 3. 组件即将销毁aboutToDisappear() {console.log('组件即将消失')// 清理定时器、释放资源}
}

第三章:项目结构详解

3.1 目录树结构

HealthyMate/
├── AppScope/                    # 应用全局配置
│   ├── app.json5               # 应用配置文件
│   └── resources/              # 全局资源
│
├── entry/                      # 主模块
│   ├── src/main/
│   │   ├── ets/               # ArkTS 源代码
│   │   │   ├── entryability/  # 应用入口
│   │   │   │   └── EntryAbility.ets
│   │   │   │
│   │   │   ├── pages/         # 页面文件
│   │   │   │   ├── Index.ets          # 主页面
│   │   │   │   ├── SplashPage.ets     # 启动页
│   │   │   │   ├── GuidePage.ets      # 引导页
│   │   │   │   ├── LoginPage.ets      # 登录页
│   │   │   │   │
│   │   │   │   ├── comp/              # 公共组件
│   │   │   │   │   ├── HomeComp.ets       # 首页组件
│   │   │   │   │   ├── CardComp.ets       # 卡片组件
│   │   │   │   │   ├── TopNavComp.ets     # 导航栏
│   │   │   │   │   └── ...
│   │   │   │   │
│   │   │   │   ├── model/             # 数据模型
│   │   │   │   │   ├── NavCard.ets        # 导航卡片模型
│   │   │   │   │   └── HealthyData.ets    # 健康数据模型
│   │   │   │   │
│   │   │   │   ├── view/              # 视图组件
│   │   │   │   │   ├── LineCharts.ets     # 折线图
│   │   │   │   │   └── BarCharts.ets      # 柱状图
│   │   │   │   │
│   │   │   │   ├── home/              # 首页相关页面
│   │   │   │   ├── healthydiscover/   # 健康发现页面
│   │   │   │   └── profile/           # 个人中心页面
│   │   │   │
│   │   │   └── utils/         # 工具类
│   │   │
│   │   ├── resources/         # 资源文件
│   │   │   ├── base/          # 基础资源
│   │   │   │   ├── element/       # 元素资源
│   │   │   │   │   ├── string.json    # 字符串资源
│   │   │   │   │   ├── color.json     # 颜色资源
│   │   │   │   │   └── float.json     # 尺寸资源
│   │   │   │   ├── media/         # 媒体资源(图片等)
│   │   │   │   └── profile/       # 配置文件
│   │   │   │       └── main_pages.json
│   │   │   │       └── route_map.json
│   │   │   ├── zh_CN/         # 中文资源
│   │   │   └── en_US/         # 英文资源
│   │   │
│   │   └── module.json5       # 模块配置文件
│   │
│   └── build-profile.json5    # 构建配置
│
├── oh_modules/                # 第三方依赖
│   └── @ohos/
│       └── mpchart/          # 图表库
│
├── oh-package.json5          # 包配置文件
└── hvigorfile.ts            # 构建脚本

3.2 关键文件说明

3.2.1 module.json5 - 模块配置
{"module": {"name": "entry","type": "entry","routerMap": "$profile:route_map","mainElement": "EntryAbility","pages": "$profile:main_pages",// 权限声明"requestPermissions": [{"name": "ohos.permission.INTERNET"  // 网络权限},{"name": "ohos.permission.LOCATION"  // 位置权限}]}
}

重要配置项:

  • mainElement: 指定应用入口 Ability
  • pages: 页面配置文件路径
  • routerMap: 路由配置文件路径
  • requestPermissions: 权限声明列表
3.2.2 main_pages.json - 页面配置
{"src": ["pages/SplashPage","pages/GuidePage","pages/Index","pages/LoginPage"]
}
3.2.3 route_map.json - 路由配置
{"routerMap": [{"name": "HealthCheckPage","pageSourceFile": "src/main/ets/pages/home/HealthCheckPage.ets"}]
}

第四章:从零开始 - 启动流程

4.1 应用入口:EntryAbility

文件位置: entry/src/main/ets/entryability/EntryAbility.ets

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';export default class EntryAbility extends UIAbility {// 1. Ability 创建时调用onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {console.log('应用创建')}// 2. 窗口阶段创建onWindowStageCreate(windowStage: window.WindowStage): void {// 加载启动页windowStage.loadContent('pages/SplashPage', (err) => {if (err.code) {console.error('加载页面失败:', err)return;}console.log('页面加载成功')});}// 3. 前台onForeground(): void {console.log('应用进入前台')}// 4. 后台onBackground(): void {console.log('应用进入后台')}// 5. 销毁onDestroy(): void {console.log('应用销毁')}
}

知识点总结:

  • UIAbility 是应用/服务的基类
  • onWindowStageCreate 中加载首页
  • ✅ 通过 loadContent 指定启动页面

4.2 启动页实现:SplashPage

文件位置: entry/src/main/ets/pages/SplashPage.ets

import { router } from "@kit.ArkUI";@Entry
@Component
struct SplashPage {@State countDown: number = 5        // 倒计时秒数@State isAutoJump: boolean = true   // 是否自动跳转private timerID: number = 0         // 定时器ID// 页面显示时启动定时器aboutToAppear() {this.startCountdown()}// 页面隐藏时清除定时器aboutToDisappear() {clearInterval(this.timerID)}// 启动倒计时private startCountdown() {this.timerID = setInterval(() => {if (this.countDown > 0) {this.countDown -= 1          // 每秒减1} else {clearInterval(this.timerID)  // 清除定时器this.jumpToHome()            // 跳转}}, 1000)}// 手动跳过private skipGuide() {clearInterval(this.timerID)this.jumpToHome()}// 跳转到引导页private jumpToHome() {router.pushUrl({ url: 'pages/GuidePage' })}build() {Stack({ alignContent: Alignment.TopEnd }) {// 主内容区域Column() {Column({ space: 12 }) {Image($r("app.media.icon_app")).width(140).height(160)Text('泰克健康').fontSize(38).fontColor('#000000').margin({ top: 10 })Text('— 安全守护 —').fontSize(38).fontColor('#A5ABCE')}.width(238).height(328)}.justifyContent(FlexAlign.Center).height('100%').width('100%')// 右上角跳过按钮Button() {Text('跳过 ' + this.countDown.toString()).fontSize(18).fontColor('#FFFFFF')}.margin({ top: 20, right: 12 }).padding({ left: 10, top: 5, right: 10, bottom: 5 }).borderRadius(15).backgroundColor('#33000000').onClick(() => {this.skipGuide()})}}
}

知识点详解:

4.2.1 装饰器用法
@Entry        // 标记为页面入口,可以独立运行
@Component    // 标记为自定义组件
struct SplashPage { }
4.2.2 状态管理
@State countDown: number = 5
// @State 装饰的变量改变时,会自动触发UI刷新
4.2.3 生命周期钩子
aboutToAppear()      // 组件即将出现(页面加载后)
aboutToDisappear()   // 组件即将消失(页面销毁前)
4.2.4 定时器使用
// 启动定时器
this.timerID = setInterval(() => {// 每秒执行
}, 1000)// 清除定时器(重要!防止内存泄漏)
clearInterval(this.timerID)
4.2.5 Stack 布局
Stack({ alignContent: Alignment.TopEnd }) {// 子组件会堆叠在一起Column() { }  // 第一层:主内容Button() { }  // 第二层:浮动按钮
}

Stack 对齐方式:

  • Alignment.TopStart - 左上角
  • Alignment.TopEnd - 右上角
  • Alignment.Center - 居中
  • Alignment.BottomEnd - 右下角

4.3 引导页实现:GuidePage

文件位置: entry/src/main/ets/pages/GuidePage.ets

import GuideComp from './comp/GuideComp'
import { router } from '@kit.ArkUI'// 引导页数据接口
interface GuideItem {currentIndex: numberguideText1: stringguideText2: stringguideImage: ResourceStrisDisplayBtn?: boolean
}// 引导页数据
const guideArrays: GuideItem[] = [{currentIndex: 0,guideText1: '智能检测',guideText2: '让健康生活触手可及',guideImage: $r('app.media.guide01')},{currentIndex: 1,guideText1: '按时服药,健康相伴',guideText2: '您的智能健康提醒专家',guideImage: $r('app.media.guide02')},{currentIndex: 2,guideText1: '您的个人健康档案',guideText2: '一目了然',guideImage: $r('app.media.guide03'),isDisplayBtn: true  // 最后一页显示按钮}
]@Entry
@Component
struct GuidePage {@State currentIndex: number = 0@State guideList: GuideItem[] = guideArraysbuild() {Swiper() {ForEach(this.guideList, (item: GuideItem, index) => {Column() {// 引导内容组件GuideComp({currentIndex: item.currentIndex,guideText1: item.guideText1,guideText2: item.guideText2,guideImage: item.guideImage})// 最后一页显示按钮if (index === this.guideList.length - 1) {Button('立即开启', { type: ButtonType.Normal }).onClick(() => {router.pushUrl({ url: 'pages/RegisterPage' })}).padding({ top: 12, bottom: 12 }).width('88%').borderRadius(12).linearGradient({angle: 180,colors: [[0x9AE0FF, 0.0],[0x526AF3, 1.0]]})}}.width('100%').height('100%')})}.loop(false)                    // 不循环.autoPlay(false)                // 不自动播放.cachedCount(3)                 // 缓存页面数.disableSwipe(this.currentIndex === this.guideList.length - 1).indicator(                     // 指示器样式Indicator.dot().itemWidth(15).itemHeight(15).selectedItemWidth(30).selectedItemHeight(15).selectedColor(0x5F80F5)).onChange((index: number) => {this.currentIndex = index     // 更新当前索引})}
}

知识点详解:

4.3.1 Swiper 轮播组件
Swiper() {// 子组件
}
.loop(false)           // 是否循环
.autoPlay(false)       // 是否自动播放
.indicator()           // 指示器
.onChange()            // 页面切换回调
4.3.2 ForEach 列表渲染
ForEach(arr: Array<T>,                    // 数据源itemGenerator: (item: T, index: number) => void,  // 渲染函数keyGenerator?: (item: T, index: number) => string // 键生成函数
)// 示例
ForEach(this.guideList, (item: GuideItem, index) => {Text(item.guideText1)
}, (item: GuideItem) => item.currentIndex.toString())
4.3.3 条件渲染
if (condition) {// 条件为真时显示Button('显示')
}
4.3.4 渐变效果
.linearGradient({angle: 180,          // 渐变角度colors: [[Color1, 0.0],    // 起始颜色和位置[Color2, 1.0]     // 结束颜色和位置]
})

第五章:UI组件开发

5.1 主页面结构:Index

文件位置: entry/src/main/ets/pages/Index.ets

import HomeComp from './comp/HomeComp'
import DiscoverComp from './comp/DiscoverComp'
import DataComp from './comp/DataComp'
import ProfileComp from './comp/ProfileComp'// 设置全局状态
AppStorage.setOrCreate('CurrentTabIndex', 0);
AppStorage.setOrCreate('IsLoggedIn', false);@Entry
@Component
struct Index {// 创建路由栈pathStack: NavPathStack = new NavPathStack();aboutToAppear(): void {// 将路由栈存储到全局AppStorage.setOrCreate("pathStack", this.pathStack);}@State currentIndex: number = 0// 底部导航图标private navIcons = [$r('app.media.icon_home_svg'),$r('app.media.icon_discover_svg'),$r('app.media.icon_data_svg'),$r('app.media.icon_profile_svg')]build() {Navigation(this.pathStack) {Tabs({ barPosition: BarPosition.End }) {TabContent() {HomeComp()}.tabBar(this.tabBarBuilder('首页', 0))TabContent() {DiscoverComp()}.tabBar(this.tabBarBuilder('健康发现', 1))TabContent() {DataComp()}.tabBar(this.tabBarBuilder('健康数据', 2))TabContent() {ProfileComp()}.tabBar(this.tabBarBuilder('个人中心', 3))}.onChange((index: number) => {this.currentIndex = index})}.mode(NavigationMode.Stack).hideTitleBar(true)}// 自定义 TabBar 构建函数@BuildertabBarBuilder(name: string, index: number) {Column() {Image(this.navIcons[index]).fillColor(this.currentIndex === index ? '#4D91FF' : '#A9B7D8').width(25).height(25)Text(name).fontSize(14).fontColor(this.currentIndex === index ? '#677DF7' : '#A9B7D8')}}
}

知识点详解:

5.1.1 Tabs 标签页组件
Tabs({ barPosition: BarPosition.End }) {  // 底部标签栏TabContent() {// 页面内容}.tabBar(自定义TabBar)
}
.onChange((index: number) => {// 标签切换回调
})

BarPosition 枚举:

  • BarPosition.Start - 顶部
  • BarPosition.End - 底部
5.1.2 Navigation 导航容器
Navigation(pathStack) {// 页面内容
}
.mode(NavigationMode.Stack)  // 堆栈模式
.hideTitleBar(true)          // 隐藏标题栏
5.1.3 @Builder 自定义构建函数
@Builder
tabBarBuilder(name: string, index: number) {Column() {Image(...)Text(name)}
}// 使用
.tabBar(this.tabBarBuilder('首页', 0))

@Builder 要点:

  • ✅ 可以接收参数
  • ✅ 可以在 build() 方法外定义
  • ✅ 可以复用UI结构

5.2 自定义组件:CardComp

文件位置: entry/src/main/ets/pages/comp/CardComp.ets

class NavItem {img: ResourceStrtitle: stringsubtitle?: stringconstructor(img: ResourceStr, title: string, subtitle?: string) {this.img = imgthis.title = titlethis.subtitle = subtitle}
}@Component
export default struct CardComp {// 组件属性wid: number | string = '100%'gradientStart?: stringgradientEnd?: stringpaddingValue?: Padding | Length | LocalizedPadding = 12// 接收父组件传递的数据@Prop params: NavItem// 插槽参数:接收父组件传递的UI构建函数@BuilderParam contentBuilder: (params: NavItem) => void = initBuilderbuild() {Column() {// 调用传入的构建函数this.contentBuilder(this.params)}.borderRadius(12).padding(this.paddingValue).width(this.wid).linearGradient(this.gradientStart && this.gradientEnd ? {direction: GradientDirection.Right,colors: [[this.gradientStart, 0.0],[this.gradientEnd, 1.0]]} : { colors: [] }).backgroundColor(this.gradientStart ? Color.Transparent : '#FFFFFF')}
}// 默认构建函数
@Builder
function initBuilder() {Text('默认占位符')
}

使用示例:

// 父组件中使用
CardComp({wid: '100%',gradientStart: '#CCF2FF',gradientEnd: '#FFFFFF',paddingValue: 5,params: myNavItem,           // 传递数据contentBuilder: itemContent  // 传递UI构建函数
})// 自定义内容构建函数
@Builder
function itemContent(param: NavItem) {Row({ space: 2 }) {Image(param.img).width(44).height(44)Text(param.title).fontSize(14)}.width('100%')
}

知识点详解:

5.2.1 @Prop 单向数据传递
@Prop params: NavItem// 特点:
// ✅ 父组件 → 子组件 单向传递
// ✅ 子组件修改不影响父组件
// ✅ 支持深拷贝
5.2.2 @BuilderParam 插槽
@BuilderParam contentBuilder: (params: NavItem) => void = defaultBuilder// 特点:
// ✅ 允许父组件自定义子组件的部分UI
// ✅ 类似 Vue 的 slot、React 的 children
// ✅ 可以传递参数

@BuilderParam 传参规则:

// ❌ 错误:直接调用
contentBuilder: this.myBuilder()// ✅ 正确:传递函数引用
contentBuilder: this.myBuilder// ✅ 正确:传递全局Builder
contentBuilder: globalBuilder
5.2.3 条件样式
.linearGradient(condition ? styleA : styleB
)// 示例
.backgroundColor(this.gradientStart ? Color.Transparent : '#FFFFFF'
)

5.3 复合组件:HomeComp

文件位置: entry/src/main/ets/pages/comp/HomeComp.ets

import TopNavComp from '../comp/TopNavComp'
import CardComp from '../comp/CardComp'
import { NavItem, navItems, lifeReminder, checkUp } from '../model/NavCard'@Component
export default struct HomeComp {// 获取全局路由栈pathStack: NavPathStack = AppStorage.get("pathStack") as NavPathStack;build() {NavDestination() {Column() {// 1. 顶部导航栏TopNavComp({ title: '泰克健康' })Column() {// 2. 功能网格Grid() {ForEach(navItems, (item: NavItem, index) => {GridItem() {CardComp({wid: '50%',params: item,contentBuilder: navItemContent})}.onClick(() => {this.pathStack.pushPathByName(item.url, null)})})}.rowsTemplate('1fr 1fr')    // 2行.rowsGap(5).columnsGap('2%').height('24%')// 3. 每日签到区域Text('每日签到').fontSize(21).fontWeight(FontWeight.Bold)Column() {Stack({ alignContent: Alignment.Center }) {// 背景层Column().width('100%').height('100%').borderRadius(20).backgroundImage($r('app.media.bg')).backgroundImageSize(ImageSize.FILL)// 内容层Column() {Text('每日签到,开启健康每一天').fontSize(18).fontColor('#ff3952a5')// 星期签到Row() {Flex({ direction: FlexDirection.Row }) {ForEach(['一', '二', '三', '四', '五', '六', '日'], (item: string) => {Column({ space: 10 }) {Text(`${item}`)Image($r('app.media.icon_true_green')).width(27).aspectRatio(1)}.flexGrow(1)})}}.padding(10)Button('点击签到').margin(10)}.width('100%').height('100%').padding(12).borderRadius(20).backgroundImage($r('app.media.home_meiriqiaodao2'))// 装饰图片Image($r('app.media.home_v')).width(75).height(95).position({ x: '75%', y: -55 })}}.height('30%')// 4. 生活提醒Column() {Text('生活提醒').fontSize(21).fontWeight(FontWeight.Bold)CardComp({gradientStart: '#FFEDE4',gradientEnd: '#FFFFFF',paddingValue: 5,wid: '100%',params: lifeReminder,contentBuilder: itemContent})}// 5. 体检预约Column() {Text('体检预约').fontSize(21).fontWeight(FontWeight.Bold)CardComp({gradientStart: '#CCF2FF',gradientEnd: '#FFFFFF',paddingValue: 5,wid: '100%',params: checkUp,contentBuilder: itemContent})}.onClick(() => {this.pathStack.pushPathByName('HealthCheckPage', null)})}.padding($r('app.float.global_padding_or_margin'))}.width('100%').height('100%').backgroundImage($r("app.media.bg")).backgroundImageSize(ImageSize.Cover)}}
}// 导航卡片内容
@Builder
function navItemContent(param: NavItem) {Row({ space: 2 }) {Image(param.img).width(55).height(55)Column({ space: 5 }) {Text(param.title).fontSize(21).fontWeight(FontWeight.Bold)Text(param.subtitle).fontSize(14).fontColor(Color.Gray)}.alignItems(HorizontalAlign.Start)}
}// 简单卡片内容
@Builder
function itemContent(param: NavItem) {Row({ space: 2 }) {Image(param.img).width(44).height(44)Text(param.title).fontSize(14)}.width('100%')
}

知识点详解:

5.3.1 Grid 网格布局
Grid() {ForEach(items, (item) => {GridItem() {// 网格项内容}})
}
.rowsTemplate('1fr 1fr')    // 2行,平分高度
.columnsTemplate('1fr 1fr 1fr')  // 3列,平分宽度
.rowsGap(10)                // 行间距
.columnsGap(10)             // 列间距

模板语法:

  • 1fr - 弹性单位(平分剩余空间)
  • 100px - 固定像素
  • 1fr 2fr - 按比例分配(1:2)
5.3.2 Flex 弹性布局
Flex({ direction: FlexDirection.Row,  // 方向justifyContent: FlexAlign.SpaceBetween,  // 主轴对齐alignItems: ItemAlign.Center   // 交叉轴对齐
}) {// 子组件
}

FlexDirection 枚举:

  • Row - 水平(从左到右)
  • RowReverse - 水平(从右到左)
  • Column - 垂直(从上到下)
  • ColumnReverse - 垂直(从下到上)
5.3.3 flexGrow 属性
Column() {// 内容
}
.flexGrow(1)  // 占据剩余空间
5.3.4 position 绝对定位
Image($r('app.media.icon')).width(75).height(95).position({ x: '75%', y: -55 })  // 绝对定位

第六章:状态管理

6.1 组件内部状态:@State

@Component
struct Counter {@State count: number = 0  // 状态变量build() {Column() {Text(`计数: ${this.count}`)Button('增加').onClick(() => {this.count++  // 修改状态,自动刷新UI})}}
}

@State 特点:

  • ✅ 状态改变自动刷新UI
  • ✅ 仅在当前组件内有效
  • ✅ 支持基本类型和对象类型

6.2 父子组件通信:@Prop 和 @Link

6.2.1 @Prop - 单向传递
// 父组件
@Entry
@Component
struct Parent {@State message: string = 'Hello'build() {Column() {Child({ msg: this.message })}}
}// 子组件
@Component
struct Child {@Prop msg: string  // 接收父组件数据build() {Text(this.msg)}
}

@Prop 特点:

  • ✅ 单向数据流:父 → 子
  • ✅ 子组件修改不影响父组件
  • ✅ 适合配置型数据
6.2.2 @Link - 双向绑定
// 父组件
@Entry
@Component
struct Parent {@State count: number = 0build() {Column() {Text(`父组件: ${this.count}`)Child({ count: $count })  // 使用 $ 传递引用}}
}// 子组件
@Component
struct Child {@Link count: number  // 双向绑定build() {Column() {Text(`子组件: ${this.count}`)Button('增加').onClick(() => {this.count++  // 修改会同步到父组件})}}
}

@Link 特点:

  • ✅ 双向数据流:父 ↔ 子
  • ✅ 子组件修改同步父组件
  • ✅ 使用 $ 传递引用

6.3 全局状态:AppStorage

// 设置全局状态
AppStorage.setOrCreate('CurrentTabIndex', 0)
AppStorage.setOrCreate('IsLoggedIn', false)
AppStorage.setOrCreate('pathStack', this.pathStack)// 获取全局状态
let tabIndex = AppStorage.get<number>('CurrentTabIndex')
let pathStack = AppStorage.get("pathStack") as NavPathStack// 使用 @StorageLink 装饰器
@Component
struct MyComponent {@StorageLink('CurrentTabIndex') tabIndex: number = 0build() {Text(`当前标签: ${this.tabIndex}`)}
}

AppStorage 使用场景:

  • ✅ 跨页面共享数据
  • ✅ 全局配置信息
  • ✅ 用户登录状态
  • ✅ 路由栈共享

6.4 状态管理最佳实践

// ✅ 推荐:合理使用状态
@State isLoading: boolean = false
@State userInfo: UserInfo | null = null
@State errorMsg: string = ''// ❌ 避免:不必要的状态
// 可以通过计算得到的值不需要状态
@State fullName: string = ''  // 不推荐// ✅ 推荐:使用计算属性
get fullName(): string {return this.firstName + ' ' + this.lastName
}

第七章:路由导航

7.1 router 路由跳转

7.1.1 基本用法
import { router } from '@kit.ArkUI'// 1. 跳转到新页面(保留当前页面)
router.pushUrl({url: 'pages/LoginPage'
})// 2. 跳转并传递参数
router.pushUrl({url: 'pages/DetailPage',params: {id: '123',name: '张三'}
})// 3. 替换当前页面(不保留历史)
router.replaceUrl({url: 'pages/HomePage'
})// 4. 返回上一页
router.back()// 5. 返回到指定页面
router.back({url: 'pages/Index'
})
7.1.2 接收路由参数
import { router } from '@kit.ArkUI'@Entry
@Component
struct DetailPage {@State id: string = ''@State name: string = ''aboutToAppear() {// 获取路由参数const params = router.getParams() as Record<string, string>this.id = params['id']this.name = params['name']}build() {Column() {Text(`ID: ${this.id}`)Text(`姓名: ${this.name}`)}}
}

7.2 Navigation 路由栈

7.2.1 配置路由

route_map.json:

{"routerMap": [{"name": "HealthCheckPage","pageSourceFile": "src/main/ets/pages/home/HealthCheckPage.ets","buildFunction": "HealthCheckPageBuilder"},{"name": "HospitalRankingPage","pageSourceFile": "src/main/ets/pages/home/HospitalRankingPage.ets","buildFunction": "HospitalRankingPageBuilder"}]
}
7.2.2 创建路由栈
@Entry
@Component
struct Index {pathStack: NavPathStack = new NavPathStack()aboutToAppear(): void {// 存储到全局,供其他组件使用AppStorage.setOrCreate("pathStack", this.pathStack)}build() {Navigation(this.pathStack) {// 页面内容}}
}
7.2.3 使用路由栈跳转
@Component
export default struct HomeComp {// 获取全局路由栈pathStack: NavPathStack = AppStorage.get("pathStack") as NavPathStackbuild() {NavDestination() {Column() {Button('跳转到健康检测').onClick(() => {// 跳转到命名路由this.pathStack.pushPathByName('HealthCheckPage', null)})Button('跳转并传参').onClick(() => {this.pathStack.pushPathByName('DetailPage', {id: '123',name: '测试'})})Button('返回').onClick(() => {this.pathStack.pop()})Button('清空路由栈').onClick(() => {this.pathStack.clear()})}}}
}
7.2.4 目标页面配置
// 页面文件:HealthCheckPage.ets@Builder
export function HealthCheckPageBuilder() {HealthCheckPage()
}@Component
struct HealthCheckPage {// 接收路由参数@State params: Record<string, Object> = {}aboutToAppear() {// 获取路由参数的方式// this.params = ...}build() {NavDestination() {Column() {Text('健康检测页面')}}.title('健康检测')  // 设置页面标题}
}

7.3 路由对比

特性routerNavigation
使用场景页面级跳转组件级导航
是否需要配置需要在 main_pages.json需要在 route_map.json
传参方式params 对象pushPathByName 参数
历史管理全局页面栈组件内路由栈
适用场景独立页面跳转多层级导航

第八章:数据模型

8.1 定义数据模型

文件位置: entry/src/main/ets/pages/model/NavCard.ets

// 导航卡片数据模型
class NavItem {img: ResourceStr           // 图片资源title: string             // 标题subtitle?: string         // 副标题(可选)url?: string             // 跳转路径(可选)constructor(img: ResourceStr,title: string,subtitle?: string,url?: string) {this.img = imgthis.title = titlethis.subtitle = subtitlethis.url = url}
}// 创建数据实例
const navItems: NavItem[] = [new NavItem($r('app.media.img_jiankangjiance'),'健康检测','守护生活每一刻','HealthCheckPage'),new NavItem($r('app.media.img_yaopinshouce'),'药品手册','明智用药',''),new NavItem($r('app.media.img_yiyuanpaihang'),'医院排行','选择优质资源','HospitalRankingPage')
]// 单个数据实例
const lifeReminder: NavItem = new NavItem($r('app.media.home_life_reminder_svg'),'今天需要吃降压药两粒哟'
)// 导出供其他组件使用
export { NavItem, navItems, lifeReminder }

8.2 使用数据模型

import { NavItem, navItems } from '../model/NavCard'@Component
export default struct HomeComp {build() {Column() {// 使用数据模型ForEach(navItems, (item: NavItem, index) => {Row() {Image(item.img)Column() {Text(item.title)Text(item.subtitle)}}})}}
}

8.3 接口 vs 类

// 方式1: 使用 interface(接口)
interface NavItemInterface {img: ResourceStrtitle: stringsubtitle?: string
}const item1: NavItemInterface = {img: $r('app.media.icon'),title: '标题'
}// 方式2: 使用 class(类)
class NavItemClass {img: ResourceStrtitle: stringsubtitle?: stringconstructor(img: ResourceStr, title: string, subtitle?: string) {this.img = imgthis.title = titlethis.subtitle = subtitle}// 可以添加方法getFullTitle(): string {return this.subtitle ? `${this.title} - ${this.subtitle}` : this.title}
}const item2 = new NavItemClass($r('app.media.icon'), '标题')

选择建议:

  • ✅ 使用 interface - 纯数据结构
  • ✅ 使用 class - 需要方法或构造函数

第九章:图表组件

9.1 集成第三方图表库

安装依赖:

ohpm install @ohos/mpchart

oh-package.json5:

{"dependencies": {"@ohos/mpchart": "^2.0.0"}
}

9.2 折线图实现

文件位置: entry/src/main/ets/pages/view/LineCharts.ets

import {JArrayList,XAxis,XAxisPosition,YAxis,YAxisLabelPosition,LineDataSet,LineData,Mode,LineChart,LineChartModel,LimitLine,LimitLabelPosition,EntryOhos,IAxisValueFormatter
} from '@ohos/mpchart'// 1. 自定义X轴格式化器
class WeekFormatter implements IAxisValueFormatter {private readonly weeks = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']getFormattedValue(value: number): string {return this.weeks[Math.round(value) % 7] || ''}
}@Component
export default struct LineCharts {// 2. 初始化模型private model: LineChartModel = new LineChartModel()private dataSet: LineDataSet = new LineDataSet(new JArrayList<EntryOhos>(), "高压")private dataSet2: LineDataSet = new LineDataSet(new JArrayList<EntryOhos>(), "低压")// 阈值线private limitLine1: LimitLine = new LimitLine(120, '危险阈值')private limitLine2: LimitLine = new LimitLine(50, '警戒阈值')// 3. 外部传入的数据@Prop data1: Array<number | null> = [600, 200, 1000, 620, 790, 410, 175]@Prop data2: Array<number | null> = [400, 150, 900, 200, 210, 300, 405]@Prop wid: number | string = '300'@Prop hei: number | string = '200'aboutToAppear() {// 4. 基础配置this.model.getDescription()?.setEnabled(false)  // 禁用描述this.model.setDragEnabled(true)                 // 启用拖拽// 5. 配置坐标轴this.configureAxis()// 6. 配置阈值线this.configureLimitLines()// 7. 绑定数据this.model.setData(this.generateMockData())this.model.setVisibleXRangeMaximum(7)  // 显示7个点}// 8. 坐标轴配置private configureAxis() {// X轴配置const xAxis = this.model.getXAxis()xAxis?.setPosition(XAxisPosition.BOTTOM)     // 位置:底部xAxis?.setGranularity(1)                     // 最小间隔xAxis?.setLabelCount(7, true)                // 标签数量xAxis?.setValueFormatter(new WeekFormatter()) // 自定义格式化// 左Y轴配置const leftAxis = this.model.getAxisLeft()leftAxis?.setAxisMinimum(0)                  // 最小值leftAxis?.setAxisMaximum(1000)               // 最大值leftAxis?.setPosition(YAxisLabelPosition.OUTSIDE_CHART)leftAxis?.setSpaceTop(15)                    // 顶部留白// 右Y轴禁用this.model.getAxisRight()?.setEnabled(false)}// 9. 阈值线配置private configureLimitLines() {// 危险阈值线(红色虚线)this.limitLine1.setLineWidth(3)this.limitLine1.enableDashedLine(10, 10, 0)  // 虚线样式this.limitLine1.setTextSize(12)this.limitLine1.setLabelPosition(LimitLabelPosition.RIGHT_TOP)this.limitLine1.setLineColor("#FF3D71")// 警戒阈值线(橙色虚线)this.limitLine2.setLineWidth(3)this.limitLine2.enableDashedLine(10, 10, 0)this.limitLine2.setTextSize(12)this.limitLine2.setLabelPosition(LimitLabelPosition.RIGHT_BOTTOM)this.limitLine2.setLineColor("#FFAA33")// 添加到Y轴this.model.getAxisLeft()?.addLimitLine(this.limitLine1)this.model.getAxisLeft()?.addLimitLine(this.limitLine2)}// 10. 生成数据private generateMockData(): LineData {// 第一条线数据const values = new JArrayList<EntryOhos>()for (let i = 0; i < this.data1.length; i++) {values.add(new EntryOhos(i, this.data1[i]))}// 第二条线数据const values2 = new JArrayList<EntryOhos>()for (let i = 0; i < this.data2.length; i++) {values2.add(new EntryOhos(i, this.data2[i]))}// 设置数据this.dataSet.setValues(values)this.dataSet2.setValues(values2)// 数据集1样式this.dataSet.setMode(Mode.CUBIC_BEZIER)      // 贝塞尔曲线this.dataSet.setColorByColor(Color.Blue)     // 线条颜色this.dataSet.setLineWidth(2)                 // 线宽this.dataSet.setDrawCircles(false)           // 不显示圆点this.dataSet.setDrawFilled(true)             // 填充渐变// 数据集2样式this.dataSet2.setColorByColor(Color.Green)this.dataSet2.setDrawCircles(false)this.dataSet2.setMode(Mode.CUBIC_BEZIER)// 返回数据return new LineData([this.dataSet, this.dataSet2])}// 11. 页面构建build() {Column() {LineChart({ model: this.model }).width(this.wid).height(this.hei).backgroundColor("#F5F7FA").onAppear(() => {// 启动动画this.model.animateXY(1500, 1500)})}}
}

9.3 使用图表组件

import LineCharts from '../view/LineCharts'@Component
struct DataPage {@State bpHighData: number[] = [120, 125, 118, 130, 115, 122, 119]@State bpLowData: number[] = [80, 85, 78, 90, 75, 82, 79]build() {Column() {Text('血压趋势').fontSize(20).fontWeight(FontWeight.Bold)LineCharts({wid: '100%',hei: 300,data1: this.bpHighData,data2: this.bpLowData})}}
}

知识点总结:

  • ✅ 使用第三方库需要先安装依赖
  • ✅ 图表配置包括:坐标轴、样式、数据
  • ✅ 可以自定义格式化器
  • ✅ 支持多条数据线

第十章:最佳实践

10.1 组件设计原则

10.1.1 单一职责
// ❌ 不好:一个组件做太多事
@Component
struct BadComponent {build() {Column() {// 导航栏Row() { }// 内容区Column() { }// 底部栏Row() { }}}
}// ✅ 好:拆分成多个组件
@Component
struct GoodComponent {build() {Column() {TopNav()ContentArea()BottomBar()}}
}
10.1.2 可复用性
// ✅ 好:可配置的通用组件
@Component
export default struct CustomButton {@Prop text: string@Prop bgColor: string = '#4D91FF'@Prop fontSize: number = 16onClickHandler?: () => voidbuild() {Button(this.text).backgroundColor(this.bgColor).fontSize(this.fontSize).onClick(() => {this.onClickHandler?.()})}
}

10.2 性能优化

10.2.1 减少不必要的渲染
// ❌ 不好:频繁创建对象
build() {Column() {ForEach(this.items, (item) => {Row() {Text(item.name).fontSize(this.getSize())  // 每次都调用}})}
}// ✅ 好:缓存计算结果
private cachedSize: number = 16aboutToAppear() {this.cachedSize = this.getSize()
}build() {Column() {ForEach(this.items, (item) => {Row() {Text(item.name).fontSize(this.cachedSize)  // 使用缓存}})}
}
10.2.2 LazyForEach 懒加载
class BasicDataSource implements IDataSource {private listeners: DataChangeListener[] = []public totalCount(): number {return 0}public getData(index: number): Object {return undefined}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {this.listeners.push(listener)}}unregisterDataChangeListener(listener: DataChangeListener): void {const pos = this.listeners.indexOf(listener)if (pos >= 0) {this.listeners.splice(pos, 1)}}
}@Component
struct LongList {private dataSource: BasicDataSource = new BasicDataSource()build() {List() {LazyForEach(this.dataSource, (item: Object) => {ListItem() {Text(item.toString())}}, item => item.id)}}
}

10.3 代码组织

10.3.1 文件命名
✅ 推荐命名规范:
- 组件文件: PascalCase (HomeComp.ets, CardComp.ets)
- 页面文件: PascalCase (LoginPage.ets, Index.ets)
- 工具类: camelCase (httpUtil.ets, dateUtil.ets)
- 模型类: PascalCase (NavCard.ets, UserInfo.ets)
10.3.2 目录结构
pages/
├── comp/          # 公共组件
├── model/         # 数据模型
├── view/          # 视图组件
├── utils/         # 工具函数
├── home/          # 首页模块
├── profile/       # 个人中心模块
└── common/        # 通用样式

10.4 样式管理

10.4.1 使用 @Extend 统一样式
// 定义全局样式扩展
@Extend(Text)
function primaryText() {.fontSize(16).fontColor('#333333').fontWeight(FontWeight.Normal)
}@Extend(Text)
function titleText() {.fontSize(24).fontColor('#000000').fontWeight(FontWeight.Bold)
}// 使用
build() {Column() {Text('标题').titleText()Text('内容').primaryText()}
}
10.4.2 使用资源文件

resources/base/element/color.json:

{"color": [{"name": "primary_color","value": "#4D91FF"},{"name": "text_primary","value": "#333333"}]
}

resources/base/element/float.json:

{"float": [{"name": "global_padding_or_margin","value": "16vp"},{"name": "title_font_size","value": "24fp"}]
}

使用资源:

Text('标题').fontSize($r('app.float.title_font_size')).fontColor($r('app.color.text_primary')).padding($r('app.float.global_padding_or_margin'))

10.5 错误处理

// ✅ 好:完善的错误处理
private jumpToPage(url: string) {try {router.pushUrl({ url: url })} catch (error) {console.error(`路由跳转失败: ${error.code} - ${error.message}`)// 显示错误提示promptAction.showToast({message: '页面跳转失败,请重试'})}
}

第十一章:常见问题

11.1 装饰器相关

Q1: @State 和 @Prop 的区别?
// @State: 组件内部状态
@State count: number = 0
// ✅ 修改会刷新UI
// ✅ 只在当前组件有效// @Prop: 父组件传入的属性
@Prop count: number
// ✅ 父组件修改会同步到子组件
// ✅ 子组件修改不影响父组件
Q2: 什么时候使用 @Link?
// 需要父子组件双向同步时使用
@Component
struct Child {@Link count: number  // 双向绑定build() {Button(`子组件: ${this.count}`).onClick(() => {this.count++  // 修改会同步到父组件})}
}// 父组件传递时使用 $
Child({ count: $count })

11.2 布局相关

Q3: 如何实现水平居中?
// 方法1: Column + alignItems
Column() {Text('居中文本')
}
.alignItems(HorizontalAlign.Center)// 方法2: Row + justifyContent
Row() {Text('居中文本')
}
.justifyContent(FlexAlign.Center)
Q4: 如何实现垂直居中?
// Column + justifyContent
Column() {Text('居中文本')
}
.justifyContent(FlexAlign.Center)
.height('100%')

11.3 路由相关

Q5: router 和 Navigation 有什么区别?
特性routerNavigation
跳转范围页面级组件级
配置文件main_pages.jsonroute_map.json
使用场景独立页面多层导航
返回栈全局组件内
Q6: 如何传递复杂对象?
// router 方式
router.pushUrl({url: 'pages/DetailPage',params: {user: JSON.stringify({  // 转为JSON字符串id: 1,name: '张三'})}
})// 接收时解析
const params = router.getParams()
const user = JSON.parse(params['user'])

11.4 性能相关

Q7: ForEach 和 LazyForEach 的区别?
// ForEach: 一次性渲染所有
ForEach(this.items, (item) => {ListItem() { Text(item) }
})
// ✅ 适合少量数据(< 100)
// ❌ 大量数据会卡顿// LazyForEach: 按需渲染
LazyForEach(this.dataSource, (item) => {ListItem() { Text(item) }
})
// ✅ 适合大量数据
// ✅ 性能更好

11.5 样式相关

Q8: 如何设置圆角?
Column() { }.borderRadius(12)  // 所有角.borderRadius({    // 单独设置topLeft: 12,topRight: 12,bottomLeft: 0,bottomRight: 0})
Q9: 如何设置阴影?
Column() { }.shadow({radius: 10,color: '#00000033',offsetX: 0,offsetY: 2})

总结

学习路径建议

第1周: 熟悉项目结构,理解启动流程↓
第2周: 学习组件开发,掌握装饰器↓
第3周: 深入状态管理和路由导航↓
第4周: 数据模型和图表组件↓
第5周: 最佳实践和性能优化

实践建议

  1. 动手实践: 运行项目,修改代码,观察效果
  2. 阅读文档: 查阅 HarmonyOS 官方文档
  3. 调试技巧: 使用 console.log 和 DevEco 调试工具
  4. 代码对比: 对比本项目与官方示例
  5. 循序渐进: 从简单组件开始,逐步深入

进阶方向

  • 🚀 网络请求和 HTTP 客户端
  • 🗄️ 本地数据库(Preferences、RelationalStore)
  • 📱 系统能力调用(相机、位置、通知)
  • 🎨 动画和转场效果
  • 🔐 安全和加密
  • 📦 应用打包和发布

祝学习愉快!遇到问题随时查阅本文档。 🎉

http://www.dtcms.com/a/490013.html

相关文章:

  • vscode离线下载依赖
  • Python 高效清理 Excel 空白行列:从原理到实战
  • 算法11.0
  • 工业级串口通信设计
  • 盐山网站建设广西网上办事大厅
  • 郑州高端网站制作团队大连本地网
  • Linux网络的应用层自定义协议
  • leetcode 2598 执行操作后的最大MEX
  • FFmpeg 基本API avio_read函数内部调用流程分析
  • 【计算机网络】HTTP协议核心知识梳理
  • 基于 MediaMTX 的微信小程序 Web 实时音视频实操方案
  • 《UDP网络编程完全指南:从套接字到高并发聊天室实战》
  • 关于 云服务器WindowsServer2016双击无法运行可执行程序 的解决方法
  • LeetCode每日一题——在区间范围内统计奇数数目
  • Linux内核架构浅谈43-Linux slab分配器:小内存块分配与内核对象缓存机制
  • 最好的免费发布网站wordpress 文章二维码
  • Spring Boot 3零基础教程,Spring Boot 日志格式,笔记18
  • mybatis-plus分页插件使用
  • 福建住房和城乡建设网站网站做提示框
  • 李宏毅机器学习笔记24
  • Leetcode每日一练--28
  • Vue Router 路由元信息(meta)详解
  • 列表标签之无序标签(本文为个人学习笔记,内容整理自哔哩哔哩UP主【非学者勿扰】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
  • sk13.【scikit-learn基础】-- 自定义模型与功能
  • (Spring)Spring Boot 中 @Valid 与全局异常处理器的联系详解
  • 数据库数据类型,数据值类型,字符串类型,日期类型详解
  • 怎么写网站规划方案买链接做网站 利润高吗
  • SAP MM物料主数据锁定及解锁接口分享
  • [FSCalendar] 可定制的iOS日历组件 | docs | Interface Builder
  • 中兴B860AV5.1-M2/B860AV5.2M_安卓9_S905L3SB_支持外置WIFI_线刷固件包