跟着百万博主手写 NotesApp 笔记应用——从条件渲染到页面切换的完整实战


前言

大家好,那个刚入坑鸿蒙开发的小白

跟着 AHui 博主 的 TodoApp 教程成功跑起来之后,我的信心大增!趁热打铁,我马上打开了博主的下一篇教程——NotesApp(笔记应用)

📎 参考文章鸿蒙原生应用开发–ArkUI–004 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() {
    // 后面填充
  }
}

🚨 踩坑实录 1Note | 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 的条件渲染机制

工作原理是这样的:

  1. showEditorfalse 时,NoteList() 被创建并渲染
  2. 点击"新建笔记"按钮,showEditor 变为 true
  3. @State 触发 UI 重新渲染
  4. 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)
}

🚨 踩坑实录 2layoutWeight(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';
}

💡 学到的新技巧

  1. maxLines(1) + textOverflow(Ellipsis):文字超出 1 行就显示省略号——笔记卡片标题不能换行
  2. substring(0, 50) + '...':内容预览只取前 50 字
  3. 颜色透明度后缀 +'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) 自动填充
滚动行为 不滚动 内容过多时自动滚动
常用属性 placeholdertype maxLengthlineHeight

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/
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐