NotesApp 笔记应用04——从条件渲染到页面切换的完整实战
鸿蒙笔记应用开发实战摘要 这篇教程完整记录了一个鸿蒙新手如何跟随博主开发NotesApp笔记应用的过程。相比之前的TodoApp,NotesApp引入了多页面切换、分类管理和搜索功能,技术难度明显提升。文章详细分解了开发步骤: 数据结构设计 - 定义Note接口,包含标题、内容、分类等字段 状态管理 - 使用@State管理笔记列表、编辑状态和分类筛选 页面切换 - 通过if/else条件渲染实现
跟着百万博主手写 NotesApp 笔记应用——从条件渲染到页面切换的完整实战
前言
大家好,那个刚入坑鸿蒙开发的小白。
跟着 AHui 博主 的 TodoApp 教程成功跑起来之后,我的信心大增!趁热打铁,我马上打开了博主的下一篇教程——NotesApp(笔记应用)。
看完文章简介我就兴奋了——这次要学的是 多页面切换、分类管理、搜索功能,比 TodoApp 又上了一个台阶!
毫不夸张地说,这篇教程让我对 ArkUI 的理解从"会写列表"进化到了"会做多页面应用"。下面就是我的完整学习记录,包括所有踩坑经历。
一、这篇教程在教什么?
先说说 NotesApp 和 TodoApp 的区别,这样你就能理解为什么我急着学它:
| 对比维度 | TodoApp(上一篇) | NotesApp(这一篇) |
|---|---|---|
| 页面数量 | 1 个页面 | 2 个页面(列表页 + 编辑页) |
| 页面切换 | 无(全部在 build 里) | 条件渲染 if/else |
| 文本输入 | TextInput(单行) | TextArea(多行) |
| 数据管理 | 增删改查 | 增删改查 + 分类 + 搜索 |
| 新增组件 | Button、Checkbox | TextArea、Search |
| 代码组织 | 全写一起 | @Builder 拆分模块 |
| 动画 | animateTo | Transition 页面转场 |
简单说:TodoApp 是"单页应用",NotesApp 是"多页应用",难度上了一个台阶。
二、环境准备(这次快多了!)
有了上次的经验,这次搭建环境只花了 10 分钟。
2.1 创建 NotesApp 项目
1. 打开 DevEco Studio
2. 点击 "Create HarmonyOS Project"
3. 选 "Empty Ability" 模板
4. 项目名称:NotesApp
5. Bundle Name:com.example.notesapp
6. API:23(ArkTS)
7. 点击 Finish
📸 截图说明:
[截图] Create Project 界面 - 项目名称输入 NotesApp - API 版本选择 23
2.2 项目结构
轻车熟路了,直接定位到 entry/src/main/ets/pages/Index.ets——这就是我们写代码的地方。
NotesApp/
├── entry/src/main/ets/
│ ├── entryability/ # 应用入口(不用动)
│ └── pages/
│ └── Index.ets # ★ 我们的战场(所有代码写这里)
三、实战:从零写 NotesApp(跟着博主一步步来)
3.1 第一步:定义数据结构
和 TodoApp 一样,先定义笔记的数据模型:
// 定义笔记的数据结构
interface Note {
id: number; // 唯一标识
title: string; // 笔记标题
content: string; // 笔记内容(这次用多行文本!)
category: string; // 分类:工作/生活/学习/其他
createdAt: string; // 创建时间
}
这次多了两个新字段:content(多行内容)和 category(分类)。
🤔 我的理解:
interface不仅仅是"定义形状",它更是一种契约。写Note接口的时候,我就已经在脑子里规划好了:“每个笔记必须有标题、内容、分类和时间”。这样后面写代码的时候就不会遗漏字段。
让我对比一下上次和这次:
// TodoApp 的数据(上次)
interface TodoItem {
id: number;
text: string; // 只有一个文本字段
completed: boolean;
createdAt: string;
}
// NotesApp 的数据(这次)
interface Note {
id: number;
title: string; // 标题单独一个字段
content: string; // 内容也单独一个字段(长文本!)
category: string; // 还多了分类
createdAt: string;
}
📌 学到的新知识:数据结构的设计决定了后面代码的复杂度。TodoApp 的每个事项只有"一句话",所以一个 text 字段就够了。但笔记需要标题和正文分开,因为它们的显示方式不同——标题用大字、正文用小字。这就是 “数据驱动 UI” 的起点。
3.2 第二步:定义分类和搜索状态
博主的文章里定义了一些 @State 变量,我跟着敲:
@Entry
@Component
struct NotesApp {
// ===== 核心数据 =====
@State notes: Note[] = []; // 所有笔记
@State nextId: number = 1; // 下一个 ID
// ===== 页面切换 =====
@State showEditor: boolean = false; // 是否显示编辑器
@State editingNote: Note | null = null; // 正在编辑的笔记
// ===== 编辑页状态 =====
@State title: string = ''; // 编辑中的标题
@State content: string = ''; // 编辑中的内容
@State selectedCategory: string = '其他'; // 选中的分类
// ===== 列表页状态 =====
@State filterCategory: string = '全部'; // 分类筛选
@State searchText: string = ''; // 搜索关键词
build() {
// 后面填充
}
}
🚨 踩坑实录 1:
Note | null这个写法我第一次见。后来查了才知道,这是 ArkTS(TypeScript)的 联合类型,表示editingNote可以是Note类型,也可以是null(没有正在编辑的笔记)。我一开始写成了
editingNote: Note = null,编译直接报错——因为Note类型不能赋值为null,必须显式声明 “允许为空”。
3.3 第三步:构建页面骨架——认识条件渲染
这次最大的不同是——有两个页面!
博主的思路很清晰:用 if/else 条件渲染来做页面切换:
build() {
Column() {
if (this.showEditor) {
// 情况 A:显示编辑器页面
this.NoteEditor()
} else {
// 情况 B:显示列表页面
this.NoteList()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F8FAFC')
}
💡 深度理解(条件渲染):
if/else在 ArkUI 的build()里不是普通的条件语句——它是 ArkUI 的条件渲染机制。工作原理是这样的:
- 当
showEditor为false时,NoteList()被创建并渲染- 点击"新建笔记"按钮,
showEditor变为true- @State 触发 UI 重新渲染
NoteList()被销毁,NoteEditor()被创建这不就是页面跳转吗? 对的!对于简单应用,用
if/else做页面切换完全够用,而且比路由跳转更轻量。当然,如果应用页面多了,就要用 Router 或 NavPathStack了——那是以后学的内容。
3.4 第四步:写列表页面(NoteList @Builder)
博主用 @Builder 将列表页面独立封装:
@Builder
NoteList() {
Column() {
// ===== 1. 头部 =====
Row() {
Text('我的笔记')
.fontSize(28)
.fontWeight(FontWeight.Bold)
Blank() // 弹性空白:把按钮推到右边
Button('+ 新建')
.backgroundColor('#6366F1')
.borderRadius(8)
.fontColor('#FFFFFF')
.onClick(() => {
this.openEditor(); // 打开编辑器(新建模式)
})
}
.width('100%')
.padding({ top: 20, bottom: 15 })
// ===== 2. 搜索栏 =====
TextInput({ placeholder: '搜索笔记...', text: this.searchText })
.width('100%')
.height(44)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.padding({ left: 16, right: 16 })
.onChange((value: string) => {
this.searchText = value;
})
// ===== 3. 分类筛选标签 =====
Row() {
this.CategoryTab('全部')
this.CategoryTab('工作')
this.CategoryTab('生活')
this.CategoryTab('学习')
this.CategoryTab('其他')
}
.width('100%')
.padding({ top: 12, bottom: 12 })
// ===== 4. 笔记列表 =====
if (this.getFilteredNotes().length === 0) {
// 空状态提示
Text('还没有笔记,点击右上角新建吧 ✍️')
.fontSize(14)
.fontColor('#CCC')
.margin({ top: 80 })
.width('100%')
.textAlign(TextAlign.Center)
} else {
List({ space: 12 }) {
ForEach(this.getFilteredNotes(), (note: Note) => {
ListItem() {
this.NoteCard(note)
}
})
}
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.padding(20)
}
🚨 踩坑实录 2:
layoutWeight(1)这个属性我一开始没写,结果列表只显示了一行。后来看了博主的代码才发现——layoutWeight(1)的作用是"占据父容器的剩余空间",如果没有它,List 的高度就是 0,自然不会滚动。这是 ArkUI 布局里非常容易漏掉的一个属性!
3.5 第五步:写分类标签(@Builder 带参数)
分类标签用了 @Builder 的参数传递:
@Builder
CategoryTab(category: string) {
Text(category)
.fontSize(13)
.fontColor(this.filterCategory === category ? '#6366F1' : '#666')
.fontWeight(this.filterCategory === category ? FontWeight.Bold : FontWeight.Normal)
.padding({ top: 4, bottom: 4, left: 12, right: 12 })
.backgroundColor(
this.filterCategory === category ? '#EEF2FF' : '#F0F0F0'
)
.borderRadius(12)
.onClick(() => {
this.filterCategory = category;
})
}
🤔 我的理解:
@Builder带参数相当于一个可以复用 UI 的函数。如果不用@Builder,这 5 个分类标签我要写 5 遍一模一样的代码(每遍 8 行),总共 40 行。用了@Builder,只用 15 行就搞定了。
3.6 第六步:写笔记卡片(NoteCard @Builder)
笔记卡片是列表里的核心 UI,每个笔记显示为一张卡片:
@Builder
NoteCard(note: Note) {
Row() {
// 左侧分类指示色块
Column()
.width(4)
.height('100%')
.backgroundColor(this.getCategoryColor(note.category))
.borderRadius(2)
// 右侧内容
Column() {
// 标题
Text(note.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1E293B')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 内容预览(取前50字)
Text(note.content.length > 50
? note.content.substring(0, 50) + '...'
: note.content
)
.fontSize(14)
.fontColor('#94A3B8')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.padding({ top: 4 })
// 底部:分类标签 + 时间
Row() {
Text(note.category)
.fontSize(11)
.fontColor(this.getCategoryColor(note.category))
.backgroundColor(this.getCategoryColor(note.category) + '20')
.padding({ top: 2, bottom: 2, left: 6, right: 6 })
.borderRadius(4)
Blank()
Text(note.createdAt)
.fontSize(11)
.fontColor('#CBD5E1')
}
.width('100%')
.padding({ top: 8 })
}
.layoutWeight(1)
.padding({ left: 12, right: 8, top: 12, bottom: 12 })
// 编辑按钮
Button() {
Text('✎').fontSize(16).fontColor('#6366F1')
}
.backgroundColor('transparent')
.width(36)
.height(36)
.onClick(() => {
this.openEditor(note); // 传 note 表示编辑模式
})
// 删除按钮
Button() {
Text('✕').fontSize(14).fontColor('#FF6B6B')
}
.backgroundColor('transparent')
.width(36)
.height(36)
.onClick(() => {
this.deleteNote(note.id);
})
}
.width('100%')
.height(100) // 固定卡片高度,统一好看
.backgroundColor('#FFFFFF')
.borderRadius(12)
.alignItems(VerticalAlign.Top)
}
辅助方法:
getCategoryColor(category: string): string {
const colors: Record<string, string> = {
'工作': '#6366F1', // 紫色
'生活': '#10B981', // 绿色
'学习': '#F59E0B', // 黄色
'其他': '#6B7280' // 灰色
};
return colors[category] || '#6B7280';
}
💡 学到的新技巧:
maxLines(1) + textOverflow(Ellipsis):文字超出 1 行就显示省略号——笔记卡片标题不能换行substring(0, 50) + '...':内容预览只取前 50 字- 颜色透明度后缀
+'20':给颜色值加 20(16进制)就是 12% 透明度的背景色,非常巧妙地实现了"浅色标签背景"
3.7 第七步:写编辑器页面(NoteEditor)
编辑器页面是这次最大的新内容——用到了 TextArea(多行输入框):
@Builder
NoteEditor() {
Column() {
// ===== 1. 顶部导航栏 =====
Row() {
Button('← 返回')
.backgroundColor('transparent')
.fontColor('#6366F1')
.fontSize(16)
.onClick(() => {
this.saveAndGoBack(); // 保存并返回
})
Blank()
Text(this.editingNote ? '编辑笔记' : '新建笔记')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Blank()
}
.width('100%')
.padding({ top: 16, bottom: 16 })
// ===== 2. 分类选择器 =====
Row() {
Text('分类:').fontSize(14).fontColor('#666')
this.CategorySelector('工作')
this.CategorySelector('生活')
this.CategorySelector('学习')
this.CategorySelector('其他')
}
.width('100%')
.padding({ bottom: 12 })
// ===== 3. 标题输入(单行 TextInput)=====
TextInput({ placeholder: '输入标题...', text: this.title })
.width('100%')
.height(48)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.padding({ left: 16, right: 16 })
.onChange((value: string) => {
this.title = value;
})
// ===== 4. 内容输入(多行 TextArea!)=====
TextArea({
placeholder: '开始写作...',
text: this.content
})
.width('100%')
.layoutWeight(1) // 占据剩余所有空间
.fontSize(16)
.lineHeight(26) // 行间距,阅读更舒适
.backgroundColor('#FFFBEB') // 淡黄色,模拟纸张
.borderRadius(16)
.margin({ top: 12 })
.padding(16)
.onChange((value: string) => {
this.content = value;
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#FFFFFF')
}
分类选择器:
@Builder
CategorySelector(category: string) {
Text(category)
.fontSize(13)
.fontColor(this.selectedCategory === category ? '#FFFFFF' : '#666')
.backgroundColor(
this.selectedCategory === category
? this.getCategoryColor(category)
: '#F0F0F0'
)
.padding({ top: 4, bottom: 4, left: 10, right: 10 })
.borderRadius(8)
.margin({ left: 4 })
.onClick(() => {
this.selectedCategory = category;
})
}
🚨 踩坑实录 3(TextArea 的迷之高度):
第一次写 TextArea 时,我给了
height(200),结果发现输入很多文字后,内容被截断了,要手动拖动才能看到。后来看了博主的代码,用的是
layoutWeight(1)——让 TextArea 自动占满剩余空间,随着内容增多,整个 Column 不会溢出,因为 TextArea 本身就是可滚动的!教训:TextArea 用于"长文本输入"时,永远用
layoutWeight(1)而不是固定高度。
3.8 第八步:TextInput vs TextArea 对比
博主在这一节专门讲了两者的区别,我整理了一个对比表:
| 对比维度 | TextInput | TextArea |
|---|---|---|
| 行数 | 单行(自动换行?不换) | 多行(可以输入大量文字) |
| 适用场景 | 标题、用户名、搜索框 | 笔记正文、文章、评论 |
| 高度建议 | 固定高度(36~48) | layoutWeight(1) 自动填充 |
| 滚动行为 | 不滚动 | 内容过多时自动滚动 |
| 常用属性 | placeholder、type |
maxLength、lineHeight |
3.9 第九步:核心业务逻辑
// ===== 打开编辑器 =====
openEditor(note?: Note) {
if (note) {
// 编辑模式:填充已有数据
this.editingNote = note;
this.title = note.title;
this.content = note.content;
this.selectedCategory = note.category;
} else {
// 新建模式:清空
this.editingNote = null;
this.title = '';
this.content = '';
this.selectedCategory = '其他';
}
this.showEditor = true;
}
// ===== 保存并返回 =====
saveAndGoBack() {
const titleTrimmed = this.title.trim();
if (titleTrimmed === '') {
// 标题为空就不保存,直接返回
this.showEditor = false;
return;
}
if (this.editingNote) {
// 编辑已有笔记
const index = this.notes.findIndex(n => n.id === this.editingNote!.id);
if (index >= 0) {
this.notes[index].title = titleTrimmed;
this.notes[index].content = this.content;
this.notes[index].category = this.selectedCategory;
}
} else {
// 新建笔记
this.notes.push({
id: this.nextId++,
title: titleTrimmed,
content: this.content,
category: this.selectedCategory,
createdAt: new Date().toLocaleDateString()
});
}
this.showEditor = false;
}
// ===== 删除笔记 =====
deleteNote(id: number) {
animateTo({ duration: 200 }, () => {
this.notes = this.notes.filter(n => n.id !== id);
});
}
// ===== 获取筛选 + 搜索后的笔记 =====
getFilteredNotes(): Note[] {
let result = [...this.notes];
// 1. 分类筛选
if (this.filterCategory !== '全部') {
result = result.filter(n => n.category === this.filterCategory);
}
// 2. 关键词搜索
if (this.searchText.trim() !== '') {
const keyword = this.searchText.toLowerCase();
result = result.filter(n =>
n.title.toLowerCase().includes(keyword) ||
n.content.toLowerCase().includes(keyword)
);
}
// 3. 按创建时间排序(最新的在前)
result.sort((a, b) => b.id - a.id);
return result;
}
💡 学到的重要思想:搜索+筛选的组合逻辑
博主的代码里,
getFilteredNotes()先做分类筛选,再做关键词搜索,最后排序。这个链式处理的思路非常重要——先把范围缩小到某个分类,再在分类结果中搜索关键词。我一开始想的是:"搜索和筛选是不是应该分开?"但仔细一想,用户既选了分类又搜了关键词,当然要同时满足。先筛后搜 是正确且高效的实现路径。
3.10 第十步:加页面转场动画(加分项)
最后,博主还教了页面切换动画:
// 在 build() 里给编辑器页面加过渡动画
if (this.showEditor) {
this.NoteEditor()
.transition(TransitionType.Slide) // ← 滑入滑出动画!
} else {
this.NoteList()
}
🤔 我的理解:
.transition(TransitionType.Slide)是 ArkUI 的页面转场动画。当showEditor变为true时,编辑器页面会从右侧滑入;返回时从左侧滑出。这个动画是框架自动完成的,不需要写任何额外的逻辑。一行代码,高级感拉满!
四、完整代码汇总
我整理了一下完整的 Index.ets 代码结构:
// entry/src/main/ets/pages/Index.ets
interface Note {
id: number;
title: string;
content: string;
category: string;
createdAt: string;
}
@Entry
@Component
struct NotesApp {
// ===== 状态管理 =====
@State notes: Note[] = [];
@State nextId: number = 1;
@State showEditor: boolean = false;
@State editingNote: Note | null = null;
@State title: string = '';
@State content: string = '';
@State selectedCategory: string = '其他';
@State filterCategory: string = '全部';
@State searchText: string = '';
build() {
Column() {
if (this.showEditor) {
this.NoteEditor()
.transition(TransitionType.Slide)
} else {
this.NoteList()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F8FAFC')
}
// ===== @Builder 列表页 =====
@Builder NoteList() { /* ... 见上文 ... */ }
// ===== @Builder 笔记卡片 =====
@Builder NoteCard(note: Note) { /* ... 见上文 ... */ }
// ===== @Builder 分类标签 =====
@Builder CategoryTab(category: string) { /* ... 见上文 ... */ }
// ===== @Builder 编辑器页 =====
@Builder NoteEditor() { /* ... 见上文 ... */ }
// ===== @Builder 分类选择器 =====
@Builder CategorySelector(category: string) { /* ... 见上文 ... */ }
// ===== 业务方法 =====
getCategoryColor(category: string): string { /* ... */ }
openEditor(note?: Note) { /* ... */ }
saveAndGoBack() { /* ... */ }
deleteNote(id: number) { /* ... */ }
getFilteredNotes(): Note[] { /* ... */ }
}
所有代码加起来大约 230 行——比 TodoApp 多了将近一倍,但结构更清晰了。
五、运行效果
点击运行按钮(▶️),选择 P40 Pro 模拟器,等待启动……
📸 运行截图说明:
┌─────── 列表页面 ───────┐ │ 我的笔记 [+ 新建] │ │ [🔍 搜索笔记... ] │ │ [全部][工作][生活][学习] │ │ ┌────────────────────┐ │ │ ▎│ 学习 HarmonyOS │ │ | ▎│ 跟着教程学 ArkUI...│ │ | ▎│ 学习 2025/01/20 │ │ | └────────────────────┘ │ | ┌────────────────────┐ │ | ▎| 项目需求文档 | │ | ▎| 完成 NotesApp... | │ | ▎| 工作 2025/01/19 | │ | └────────────────────┘ │ | │ | [← 返回] 编辑笔记 │ | 分类:[工作][生活] │ | [学习] [其他] │ | │ | ┌──────────────────┐ │ | │ 输入标题... │ │ | └──────────────────┘ │ | │ | ┌──────────────────┐ │ | │ │ │ | │ 开始写作... │ │ | │ (淡黄色纸张背景) │ │ | │ │ │ | └──────────────────┘ │ └─────────────────────────┘
我测试了所有功能:
| 功能 | 操作方式 | 结果 |
|---|---|---|
| ✅ 新建笔记 | 点"+新建"→ 编辑 → 点"←返回" | 列表出现新笔记 |
| ✅ 编辑笔记 | 点卡片上的 ✎ 按钮 | 打开编辑器并填充数据 |
| ✅ 删除笔记 | 点卡片上的 ✕ 按钮 | 笔记消失(带动画) |
| ✅ 分类筛选 | 点"工作/生活/学习"标签 | 只显示对应分类 |
| ✅ 搜索笔记 | 在搜索框输入"Harmony" | 只显示匹配的笔记 |
| ✅ 页面切换动画 | 新建/返回时 | 滑入滑出过渡非常流畅 |
| ✅ 空状态提示 | 删完所有笔记 | 显示"还没有笔记"的提示 |
全部通过! 🎉
六、避坑指南(这次踩的坑更多了)
| 分类 | 坑的描述 | 正确做法 | 我的血泪史 |
|---|---|---|---|
| 🚨 TextArea | 给了固定高度 height(200) |
用 layoutWeight(1) |
内容多了看不到下半部分 |
| 🚨 类型定义 | Note | null 写成 Note |
必须声明可为空 | 编译报错"不能将 null 赋值给 Note" |
| 🚨 layoutWeight | 忘了加 layoutWeight(1) |
每个填充空间的组件都要加 | 列表只显示一行 |
| 🚨 搜索逻辑 | 搜索时忘了转小写 | toLowerCase() 做不区分大小写搜索 |
搜"harmony"找不到"Harmony" |
| 🚨 空状态 | 没处理列表为空的情况 | 加 if length === 0 |
列表空了显示空白,用户困惑 |
| 🚨 保存验证 | 空标题也保存了 | 加 if title === '' return |
生成了空白笔记 |
| 🚨 Transition | 动画加错了位置 | 加在条件渲染里的组件上 | 动画不生效 |
| 🚨 数组排序 | 直接用 sort() 没传比较函数 |
sort((a,b) => b.id - a.id) |
排序结果不对 |
七、学完这个项目后的核心收获
7.1 我理解的条件渲染机制
用户点击"新建笔记"
↓
openEditor() 被调用
↓
showEditor = true
↓
@State 检测到变化
↓
build() 重新执行
↓
if (true) → 显示 NoteEditor()
↓
NoteList() 被销毁 → NoteEditor() 被创建
↓
页面"跳转"完成(实际上是一个组件替换了另一个)
这就是 ArkUI 条件渲染 的工作方式。没有路由跳转、没有页面栈管理——就是一个简单的 if/else,简单但有效。
7.2 我理解的 @Builder 最佳用法
学完这篇教程,我总结了 @Builder 的使用原则:
┌────────────────────────────────────────────────────────────────┐
│ @Builder 使用原则 │
│ │
│ 1. 每个"页面"一个 @Builder → NoteList / NoteEditor │
│ 2. 每个"卡片/列表项"一个 @Builder → NoteCard │
│ 3. 每个"可复用小组件"一个 @Builder → CategoryTab / Selector │
│ │
│ ❌ 不要把 @Builder 当普通函数 │
│ ❌ 不要为了用 @Builder 而用 @Builder │
│ ✅ 当 UI 代码超过 15 行且有复用价值时,就用 @Builder │
└────────────────────────────────────────────────────────────────┘
7.3 ArkUI 多页面应用的架构思路
学完 NotesApp,我脑子里有了一个"小应用架构"的雏形:
┌─────────────────────────────────────────────────┐
│ 应用的"状态大脑" │
│ @State notes: Note[] (数据存储) │
│ @State showEditor: boolean(页面控制) │
│ @State filterCategory / searchText(筛选控制) │
└──────────────┬──────────────────────────────────┘
│
┌──────────┴──────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ NoteList │ │NoteEditor│
│ (列表页) │ ←→ │ (编辑页) │
│ @Builder │ │ @Builder │
└──────────┘ └──────────┘
│ │
│ 读取数据 │ 修改数据
▼ ▼
┌─────────────────────────────────────────────────┐
│ 业务方法层 │
│ getFilteredNotes() / saveAndGoBack() / deleteNote()│
└─────────────────────────────────────────────────┘
总结一句话:@State 是"大脑",@Builder 是"四肢",业务方法是"神经"——大脑决定做什么,神经传递指令,四肢执行动作。
官方文档:HarmonyOS 应用开发文档
- 开发者社区:华为开发者论坛
- 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net/
更多推荐




所有评论(0)