在上一篇文章中,我们已经完成了一个:
✔ 能运行
✔ 有完整页面
✔ 有组件拆分
✔ 有状态管理(V2)
✔ 有真实业务逻辑
的 任务清单 App。
但说实话------
它还不像一个"真正的应用"。
因为它缺了两样东西:
网络请求 + 数据缓存
这一篇,我们就把应用往"真实产品"方向再推进一步。
一、这一篇我们要做什么?
继续在「任务清单 App」基础上,新增 4 个能力:
-
🔗 从服务器获取任务列表
-
⏳ 页面加载状态(Loading)
-
💾 本地缓存(Preferences)
-
🛜 网络失败自动兜底(缓存 + 提示)
完成后,你的应用将具备:
"有网用网,没网用缓存"的真实体验
二、整体设计思路(非常关键)
在写代码之前,先明确职责分工:
| 层级 | 负责什么 |
|---|---|
| UI 组件 | 展示数据、响应点击 |
| Store(Model) | 数据管理、业务逻辑 |
| Network Service | 网络请求 |
| Cache Service | 本地缓存 |
| Page | 组合 & 调度 |
📌 UI 永远不直接发请求
📌 UI 永远不直接操作缓存
三、准备一个"假的后端接口"
为了教学,我们假设服务端返回的数据格式如下:
[
{ "id": 1, "title": "学习 HarmonyOS" },
{ "id": 2, "title": "写教学文章" }
]
接口地址(示例):
https://example.com/api/tasks
四、第一步:封装网络请求层(推荐写法)
新增目录结构
entry/
└─ service/
└─ TaskService.ets
service/TaskService.ets
import http from '@ohos.net.http'
import { Task } from '../model/TaskModel'
export class TaskService {
static async fetchTasks(): Promise<Task[]> {
const httpRequest = http.createHttp()
const response = await httpRequest.request(
'https://example.com/api/tasks',
{
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.STRING
}
)
const data = JSON.parse(response.result as string)
return data.map(item => new Task(item.id, item.title))
}
}
教学重点
-
网络请求单独封装
-
UI 完全不知道请求细节
-
返回的是 业务对象 Task
五、第二步:加入本地缓存(Preferences)
新增缓存工具类
entry/
└─ service/
└─ CacheService.ets
service/CacheService.ets
import preferences from '@ohos.data.preferences'
import { Task } from '../model/TaskModel'
const CACHE_KEY = 'TASK_LIST'
export class CacheService {
static async saveTasks(tasks: Task[]) {
const pref = await preferences.getPreferences(getContext(), 'task_cache')
await pref.put(CACHE_KEY, JSON.stringify(tasks))
await pref.flush()
}
static async loadTasks(): Promise<Task[] | null> {
const pref = await preferences.getPreferences(getContext(), 'task_cache')
const data = await pref.get(CACHE_KEY, '')
if (!data) return null
const list = JSON.parse(data)
return list.map(item => new Task(item.id, item.title))
}
}
📌 Preferences 非常适合:
-
少量数据
-
配置
-
列表缓存
六、第三步:升级 TaskStore(核心改造)
现在轮到 真正的"大脑" ------ Store。
修改 model/TaskModel.ets
import { TaskService } from '../service/TaskService'
import { CacheService } from '../service/CacheService'
@ObservedV2
export class TaskStore {
tasks: Task[] = []
loading: boolean = false
error: string = ''
async loadTasks() {
this.loading = true
this.error = ''
try {
const remoteTasks = await TaskService.fetchTasks()
this.tasks = remoteTasks
CacheService.saveTasks(remoteTasks)
} catch (e) {
const cache = await CacheService.loadTasks()
if (cache) {
this.tasks = cache
} else {
this.error = '加载失败,请检查网络'
}
} finally {
this.loading = false
}
}
addTask(title: string) {
this.tasks.unshift(new Task(Date.now(), title))
CacheService.saveTasks(this.tasks)
}
removeTask(id: number) {
this.tasks = this.tasks.filter(item => item.id !== id)
CacheService.saveTasks(this.tasks)
}
get count(): number {
return this.tasks.length
}
}
教学重点(非常重要)
-
Store 同时协调:
-
网络
-
缓存
-
UI 状态
-
-
UI 只关心三个字段:
-
tasks -
loading -
error
-
七、第四步:页面中触发加载(@Once)
修改 pages/Index.ets
@ComponentV2
struct Index {
@Local store = new TaskStore()
@Once
async init() {
await this.store.loadTasks()
}
build() {
Column({ space: 12 }) {
Text("📋 我的任务清单")
.fontSize(22)
if (this.store.loading) {
Text("加载中...")
} else if (this.store.error) {
Text(this.store.error).fontColor(Color.Red)
} else {
Text(`任务数:${this.store.count}`)
TaskInput({ onAdd: (title) => this.store.addTask(title) })
TaskList({
tasks: this.store.tasks,
onDelete: (id) => this.store.removeTask(id)
})
}
}
.padding(16)
}
}
📌 @Once:
👉 页面创建时只执行一次
👉 非常适合初始化请求
八、现在这个应用已经进化到什么程度?
你现在的 App 具备了:
✔ 网络请求
✔ Loading 状态
✔ 错误处理
✔ 本地缓存
✔ 离线可用
✔ 状态集中管理
✔ UI 与业务解耦
这已经是正式商用 App 的基础形态。
九、如果这是你自己的项目,下一步可以做什么?
🚀 可扩展方向
-
下拉刷新(重新调用 loadTasks)
-
网络状态监听(自动切换)
-
分页加载
-
登录态 + Token 缓存
-
请求拦截 & 统一错误码
-
数据同步策略(本地 → 云)
十、你应该真正学会的不是 API
而是这套思路:
UI 不关心数据从哪里来
Store 决定用网络还是缓存
Service 只做一件事
这是 HarmonyOS、Vue、React、Flutter 通用的工程思维。
结语
到这一篇为止,你已经不是"写 Demo"的水平了,而是:
已经在用 HarmonyOS 的正确方式写"真实应用"