










本教程基于 HarmonyOS NEXT API 12+ 企业级开发标准编写,从实战角度出发,系统讲解 ArkUI 开发的核心技术、工程实践、性能优化与项目架构,帮助开发者快速掌握企业级应用开发能力,可直接作为团队培训教材或开发参考手册。
教程说明
本教程以实战为核心,每个知识点均配套可运行的代码示例和企业级最佳实践,学习过程中建议配合DevEco Studio实际操作,效果更佳。
1. 安装DevEco Studio:下载最新正式版,安装到纯英文路径,建议路径:D:\DevEco\DevEco Studio\
2. 配置SDK:下载HarmonyOS NEXT SDK,存储到纯英文路径:D:\DevEco\Sdk\NEXT\
3. 配置模拟器:创建API 12+的模拟器,建议选择Phone设备,分辨率1080*2340
4. 环境验证:创建Hello World项目,编译运行到模拟器,确认环境正常
环境检查要点
所有路径必须是纯英文、无空格、无特殊字符;系统用户名如果是中文,必须手动修改SDK路径到非用户目录。
// 页面入口组件
@Entry
@ComponentV2
struct HelloWorld {
@Local count: number = 0;
build() {
Column() {
Text(`Hello ArkUI!`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.margin({ top: 50, bottom: 30 })
Text(`点击次数: ${this.count}`)
.fontSize(20)
.margin({ bottom: 30 })
Button("点击我")
.width(200)
.height(50)
.onClick(() => {
this.count += 1;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
实战练习 运行上述代码,点击按钮观察计数变化,理解响应式状态的基本工作原理。
ArkUI采用声明式开发范式,核心思想是 UI = f(State),即界面是状态的函数,状态变化时UI自动更新。开发者只需描述UI的目标状态,框架自动计算差异并更新。
|
组件 |
用途 |
常用属性 |
|
Text |
显示文本内容 |
fontSize、fontColor、fontWeight、maxLines |
|
Button |
按钮组件 |
type、onClick、width、height |
|
Image |
显示图片 |
src、objectFit、width、height |
|
TextInput |
输入框组件 |
placeholder、onChange、type |
|
Column/Row |
线性布局容器 |
justifyContent、alignItems、space |
|
List/ListItem |
列表容器 |
space、listDirection、onScroll |
@Entry
@ComponentV2
struct LoginPage {
@Local username: string = "";
@Local password: string = "";
@Local isLoading: boolean = false;
// 登录逻辑
handleLogin() {
if (!this.username || !this.password) {
prompt.showToast({ message: "请输入用户名和密码" });
return;
}
this.isLoading = true;
// 模拟登录请求
setTimeout(() => {
this.isLoading = false;
prompt.showToast({ message: "登录成功" });
// 跳转到首页
router.pushUrl({ url: "pages/home" });
}, 1500);
}
build() {
Column() {
// 标题
Text("用户登录")
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ top: 80, bottom: 50 })
// 用户名输入
TextInput({ placeholder: "请输入用户名" })
.width('80%')
.height(50)
.margin({ bottom: 20 })
.onChange((value) => {
this.username = value;
})
// 密码输入
TextInput({ placeholder: "请输入密码", type: InputType.Password })
.width('80%')
.height(50)
.margin({ bottom: 40 })
.onChange((value) => {
this.password = value;
})
// 登录按钮
Button(this.isLoading ? "登录中..." : "登录")
.width('80%')
.height(50)
.enabled(!this.isLoading)
.onClick(() => {
this.handleLogin();
})
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.backgroundColor('#f5f5f5')
}
}
实战练习 实现登录页面,添加"忘记密码"和"注册账号"链接,实现表单验证逻辑。
ArkUI提供多层次的布局方案,满足不同场景需求:
• 线性布局:Column/Row,最简单常用的布局方式
• 层叠布局:Stack,子组件堆叠排列
• 相对布局:RelativeContainer,复杂对齐场景
• 弹性布局:Flex,弹性分配空间
• 网格布局:Grid,网格排列场景
• 优先使用Column/Row,实现简单布局
• 复杂对齐场景使用RelativeContainer,减少布局嵌套
• 布局嵌套层级不超过5层,避免性能问题
• 使用自适应单位vp/fp,适配不同屏幕尺寸
• 避免在Scroll组件中嵌套List,会导致滚动冲突
@ComponentV2
struct ProductCard {
@Param product: Product;
build() {
Column() {
// 商品图片
Stack({ alignContent: Alignment.TopEnd }) {
Image(this.product.imageUrl)
.width('100%')
.height(180)
.objectFit(ImageFit.Cover)
// 热销标签
if (this.product.isHot) {
Text("热销")
.fontSize(12)
.fontColor(Color.White)
.backgroundColor(Color.Red)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.margin(8)
.borderRadius(4)
}
}
.borderRadius({ topLeft: 8, topRight: 8 })
// 商品信息
Column({ space: 8 }) {
Text(this.product.name)
.fontSize(14)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 4, alignItems: VerticalAlign.Center }) {
Text(`¥${this.product.price.toFixed(2)}`)
.fontSize(16)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
if (this.product.originalPrice) {
Text(`¥${this.product.originalPrice.toFixed(2)}`)
.fontSize(12)
.fontColor('#999')
.decoration({ type: TextDecorationType.LineThrough })
}
}
Row({ justifyContent: FlexAlign.SpaceBetween, alignItems: VerticalAlign.Center }) {
Text(`${this.product.sales}人已买`)
.fontSize(12)
.fontColor('#999')
Button("加入购物车")
.type(ButtonType.Capsule)
.fontSize(10)
.height(24)
.padding({ left: 8, right: 8 })
.onClick(() => {
// 加入购物车逻辑
})
}
}
.padding(10)
}
.width('48%')
.borderRadius(8)
.backgroundColor(Color.White)
.shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
}
}
• 状态最小化:仅将需要触发UI更新的数据标记为响应式状态
• 状态下推:状态尽量保存在最底层的使用组件,减少影响范围
• 单一数据源:相同状态只保存在一处,避免多数据源不一致
• 状态不可变性:修改状态时生成新对象,避免直接修改原对象
|
通信场景 |
推荐方式 |
示例 |
|
父→子传参 |
@Param |
父组件传递属性,子组件@Param接收 |
|
子→父通知 |
@Event |
子组件触发事件,父组件监听回调 |
|
跨层级传递 |
@Provider/@Consumer |
祖先组件提供,后代组件消费 |
|
全局状态 |
AppStorage/全局状态管理库 |
应用级状态共享 |
// 待办项数据结构
interface TodoItem {
id: number;
content: string;
completed: boolean;
}
// 待办项组件
@ComponentV2
struct TodoItemCard {
@Param item: TodoItem;
@Event onToggle: (id: number) => void;
@Event onDelete: (id: number) => void;
build() {
Row({ space: 10, justifyContent: FlexAlign.SpaceBetween }) {
Row({ space: 10, alignItems: VerticalAlign.Center }) {
Toggle({ type: ToggleType.Checkbox, isOn: this.item.completed })
.selectedColor(Color.Green)
.onChange((isOn) => {
this.onToggle(this.item.id);
})
Text(this.item.content)
.fontSize(16)
.decoration({ type: this.item.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
.fontColor(this.item.completed ? '#999' : '#333')
}
Button("删除")
.type(ButtonType.Capsule)
.fontSize(12)
.height(28)
.backgroundColor(Color.Red)
.onClick(() => {
this.onDelete(this.item.id);
})
}
.width('100%')
.padding(15)
.borderRadius(8)
.backgroundColor(Color.White)
.margin({ bottom: 10 })
}
}
// 主页面
@Entry
@ComponentV2
struct TodoListPage {
@Local todoList: TodoItem[] = [];
@Local inputText: string = "";
// 添加待办
addTodo() {
if (!this.inputText.trim()) {
prompt.showToast({ message: "请输入待办内容" });
return;
}
const newTodo: TodoItem = {
id: Date.now(),
content: this.inputText.trim(),
completed: false
};
this.todoList = [...this.todoList, newTodo];
this.inputText = "";
}
// 切换完成状态
toggleTodo(id: number) {
this.todoList = this.todoList.map(item => {
if (item.id === id) {
return { ...item, completed: !item.completed };
}
return item;
});
}
// 删除待办
deleteTodo(id: number) {
this.todoList = this.todoList.filter(item => item.id !== id);
}
build() {
Column() {
// 顶部输入区域
Row({ space: 10 }) {
TextInput({ placeholder: "输入待办事项", text: this.inputText })
.layoutWeight(1)
.height(44)
.onChange((value) => {
this.inputText = value;
})
.onSubmit(() => {
this.addTodo();
})
Button("添加")
.width(80)
.height(44)
.onClick(() => {
this.addTodo();
})
}
.width('100%')
.padding(15)
.backgroundColor(Color.White)
// 统计信息
Row({ justifyContent: FlexAlign.SpaceBetween }) {
Text(`总共: ${this.todoList.length} 项`)
.fontSize(14)
Text(`已完成: ${this.todoList.filter(item => item.completed).length} 项`)
.fontSize(14)
}
.width('100%')
.padding(15)
// 待办列表
List({ space: 10 }) {
ForEach(this.todoList, (item: TodoItem) => {
ListItem() {
TodoItemCard({
item: item,
onToggle: (id) => this.toggleTodo(id),
onDelete: (id) => this.deleteTodo(id)
})
}
}, (item: TodoItem) => item.id.toString())
}
.layoutWeight(1)
.padding(15)
.backgroundColor('#f5f5f5')
}
.width('100%')
.height('100%')
}
}
实战练习 实现Todo List的全选、批量删除、本地持久化存储功能。
// 网络请求工具类
class HttpUtil {
private static baseUrl: string = "https://api.example.com";
private static timeout: number = 30000;
// GET请求
static async get(url: string, params?: Record): Promise { // 拼接查询参数 if (params) { const queryString = new URLSearchParams(params).toString(); url += `?${queryString}`; } const response = await fetch(`${this.baseUrl}${url}`, { method: 'GET', headers: this.getHeaders(), timeout: this.timeout }); return this.handleResponse(response); } // POST请求 static async post(url: string, data?: any): Promise { const response = await fetch(`${this.baseUrl}${url}`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(data), timeout: this.timeout }); return this.handleResponse(response); } // 获取请求头 private static getHeaders(): Record { const headers: Record = { 'Content-Type': 'application/json' }; // 添加token const token = AppStorage.get('token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } // 处理响应 private static async handleResponse(response: Response): Promise { const result = await response.json() as ApiResponse; if (result.code === 200) { return result.data; } else if (result.code === 401) { // token过期,跳转到登录页 router.clear(); router.pushUrl({ url: "pages/login" }); throw new Error("登录已过期,请重新登录"); } else { prompt.showToast({ message: result.message || "请求失败" }); throw new Error(result.message || "请求失败"); } } } // API响应类型 interface ApiResponse { code: number; message: string; data: T; }
// 商品API
class ProductApi {
// 获取商品列表
static getProductList(params: { page: number; pageSize: number; keyword?: string }): Promise { return HttpUtil.get("/product/list", params); } } @Entry @ComponentV2 struct ProductListPage { @Local productList: Product[] = []; @Local page: number = 1; @Local pageSize: number = 20; @Local isLoading: boolean = false; @Local hasMore: boolean = true; @Local keyword: string = ""; aboutToAppear() { this.loadData(); } // 加载数据 async loadData(isRefresh: boolean = false) { if (this.isLoading) return; this.isLoading = true; try { const params = { page: isRefresh ? 1 : this.page, pageSize: this.pageSize, keyword: this.keyword }; const result = await ProductApi.getProductList(params); if (isRefresh) { this.productList = result; this.page = 2; } else { this.productList = [...this.productList, ...result]; this.page += 1; } this.hasMore = result.length === this.pageSize; } catch (e) { console.error("加载商品列表失败", e); } finally { this.isLoading = false; } } build() { Column() { // 搜索框 TextInput({ placeholder: "搜索商品", text: this.keyword }) .width('90%') .height(40) .margin(15) .borderRadius(20) .backgroundColor('#f0f0f0') .onChange((value) => { this.keyword = value; }) .onSubmit(() => { this.loadData(true); }) // 商品列表 List({ space: 15 }) { // 下拉刷新 Refresh({ refreshing: this.isLoading && this.page === 1 }) .onRefresh(() => { this.loadData(true); }) // 商品网格 GridRow({ columns: 2, gutter: 15 }) { ForEach(this.productList, (item: Product) => { GridCol() { ProductCard({ product: item }) } }, (item: Product) => item.id.toString()) } .padding(15) // 加载更多 if (this.hasMore && this.productList.length > 0) { ListItem() { Row({ justifyContent: FlexAlign.Center }) { if (this.isLoading) { LoadingProgress() .width(20) .height(20) .margin({ right: 8 }) Text("加载中...") .fontSize(14) .fontColor('#999') } else { Text("上拉加载更多") .fontSize(14) .fontColor('#999') } } .height(50) .onAppear(() => { if (!this.isLoading && this.hasMore) { this.loadData(); } }) } } // 空状态 if (!this.isLoading && this.productList.length === 0) { ListItem() { Column({ space: 10, justifyContent: FlexAlign.Center }) { Image($r('app.media.empty')) .width(100) .height(100) .objectFit(ImageFit.Contain) Text("暂无商品") .fontSize(14) .fontColor('#999') } .height(300) } } } .layoutWeight(1) } .width('100%') .height('100%') .backgroundColor('#f5f5f5') } }
• 长列表优化:使用LazyForEach配合reuseId实现组件复用,避免使用ForEach渲染大量数据
• 减少重渲染:合理拆分组件,状态最小化,避免不必要的组件重建
• build()方法优化:禁止在build()中执行复杂计算、网络请求、日志输出等操作
• 图片优化:使用合适分辨率的图片,开启内存缓存,及时释放不可见图片
• 资源释放:在aboutToDisappear中清理定时器、事件监听、订阅等资源
1. ✅ 长列表使用LazyForEach,每个ListItem设置唯一reuseId
2. ✅ build()方法中无复杂计算和副作用操作
3. ✅ 组件嵌套层级不超过5层
4. ✅ 频繁切换的组件使用Visibility控制,而非if/else
5. ✅ 所有定时器、事件监听在aboutToDisappear中正确释放
6. ✅ 图片使用.webp格式,尺寸不超过显示尺寸的2倍
7. ✅ 避免在循环中创建函数和对象
8. ✅ 复杂计算结果使用@Memo或局部变量缓存
src/main/ets/
├── common/ # 公共资源
│ ├── components/ # 基础公共组件
│ ├── utils/ # 工具函数
│ ├── constants/ # 常量定义
│ ├── types/ # 通用类型定义
│ ├── api/ # 公共API封装
│ └── styles/ # 全局样式
├── features/ # 业务特性模块
│ ├── home/ # 首页模块
│ │ ├── components/ # 首页私有组件
│ │ ├── pages/ # 首页页面
│ │ ├── viewmodels/ # 首页业务逻辑
│ │ ├── api/ # 首页API
│ │ └── types/ # 首页类型定义
│ ├── product/ # 商品模块
│ └── user/ # 用户模块
├── router/ # 路由配置
├── store/ # 全局状态管理
└── entryability/ # 应用入口配置
• 组件名、类名使用大驼峰命名,如ProductCard、HttpUtil
• 变量名、方法名使用小驼峰命名,如productList、loadData
• 常量使用全大写下划线分隔,如BASE_URL、PAGE_SIZE
• 所有变量、方法参数、返回值明确类型,禁止使用any类型
• 组件代码行数不超过300行,超过则拆分组件
• 每个组件、类、方法添加JSDoc注释,说明用途、参数、返回值
|
问题现象 |
解决方案 |
|
状态变化后UI不更新 |
检查是否使用了响应式装饰器,修改对象/数组时是否生成了新实例 |
|
列表滑动卡顿 |
替换为LazyForEach,为ListItem添加reuseId,优化item布局 |
|
页面跳转卡顿 |
将复杂初始化逻辑移到aboutToAppear,使用异步执行 |
|
图片显示异常 |
检查图片路径是否正确,是否有网络权限,图片格式是否支持 |
|
网络请求失败 |
检查是否配置了网络权限,域名是否在白名单中,参数是否正确 |
|
打包失败 |
检查路径是否包含中文,依赖是否正确安装,配置文件是否有误 |
学习建议
完成本教程所有实战练习后,建议选择一个实际业务场景(如新闻客户端、电商App、社交应用)进行完整项目开发,在实践中积累经验。遇到问题优先查阅官方文档,或在HarmonyOS开发者社区搜索解决方案。
文档版本:V1.0 | 适配版本:HarmonyOS NEXT API 12+ | 更新日期:2024年4月
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。