前端同学最容易卡在这一层:
Vue 里我有
main.ts、App.vue、vue-router;鸿蒙里突然冒出 Ability、Stage、Module、router和Navigation------它们分别对应什么? 一句话对齐:
| Web(Vue) | HarmonyOS(Stage 模型) |
|---|---|
| 整个 SPA 应用 | 一个或多个 Ability 组成的应用 |
| 浏览器窗口 / Tab | WindowStage(窗口舞台) |
router 管理页面栈 |
页面路由 router + 组件导航 Navigation |
| monorepo / 分包 / 组件库 | Module:entry / feature / HAR / HSP |
本篇按「从外到内」讲清四件事:Stage → Ability → 模块化 → 路由,并用个人的学习仓库代码对照。
一、先建立总图:应用是怎么被系统拉起来的
对照本仓库入口:系统先找到 module.json5 里的 mainElement: EntryAbility,再执行 EntryAbility,在窗口创建时加载 pages/Index。
21:36:entry/src/main/ets/entryability/EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
font.registerFont({
familyName: 'iconfont',
familySrc: $rawfile('iconfont.ttf'),
});
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
对前端的映射:
EntryAbility≈main.ts里createApp(App).mount('#app')之前的「应用壳」loadContent('pages/Index')≈ 指定第一个路由页面- 字体注册、主题色模式等「全局初始化」适合放在 Ability,而不是某个业务页
二、Stage 模型:为什么不是「只有页面生命周期」
鸿蒙当前主流是 Stage 模型 (相对早期的 FA 模型)。它把应用运行抽象成多个阶段(Stage) ,由系统统一调度资源与回调,而不是只让你在页面里写 onMounted。
结合 Stage 模型解读 的核心概念:
| 概念 | 含义 | 前端类比 |
|---|---|---|
| Stage(阶段) | 应用在系统中的运行状态单位(前台 / 后台 / 暂停 / 销毁等),对应不同资源策略 | 浏览器标签前台 / 后台 / 被回收 |
| Ability(能力组件) | 功能载体:UIAbility、ExtensionAbility 等,挂在 Stage 上跑 | 一个「可独立拉起」的子应用壳 |
| WindowStage | UIAbility 持有的窗口舞台,负责加载页面内容 | 浏览器窗口 / Electron BrowserWindow |
Stage 带来的实际好处
- 资源策略更细:前台可全量渲染;后台应暂停动画、少刷 UI;内存压力时可释放缓存。
- 多端 / 多窗口更统一:同一套 Ability + Stage 回调,便于理解跨设备、分屏场景。
- 生命周期可控:初始化、前后台切换、销毁都有明确钩子,避免「页面还在跑轮询、应用已经切后台」。
对前端的提醒:Vue 的 onMounted / onUnmounted 管不了 「整个 App 进后台」;鸿蒙要把这类逻辑放到 Ability 的 onForeground / onBackground。
三、Ability:应用能力的基本单元
3.1 常见 Ability 类型
| 类型 | 作用 | Vue / Web 类比 |
|---|---|---|
| UIAbility | 带界面的主能力,最常用 | 主 SPA 壳 |
| ExtensionAbility | 扩展能力(卡片、备份、分享等) | 插件 / Widget / 系统扩展点 |
| Service 类后台能力(按版本与 Kit 演进) | 偏后台任务、数据同步 | Web Worker / 后台 Service(注意权限与功耗) |
本仓库 module.json5 里同时声明了主 Ability 和备份 Extension:
13:48:entry/src/main/module.json5
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
...
"exported": true,
"skills": [
{
"entities": ["entity.system.home"],
"actions": ["ohos.want.action.home"]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
...
}
]
skills 里的 ohos.want.action.home 可以理解为:系统桌面点击图标时用什么 Want 拉起你------类似 Web 的「默认入口 URL」。
3.2 UIAbility 生命周期(必背)
结合官方实践与 Stage 生命周期说明,日常开发最常用的是:
| 回调 | 何时触发 | 适合做什么 | Vue 对照 |
|---|---|---|---|
onCreate |
Ability 创建 | 全局配置、读启动参数 Want | main.ts 初始化 |
onWindowStageCreate |
窗口舞台创建 | loadContent、注册字体 |
挂载根组件 |
onForeground |
回到前台 | 恢复动画、刷新关键数据 | visibilitychange / 回前台 |
onBackground |
进入后台 | 暂停定时器、保存草稿、停动画 | 切后台 |
onWindowStageDestroy |
窗口销毁 | 释放 UI 相关资源 | 卸载根 |
onDestroy |
Ability 销毁 | 取消订阅、清全局资源 | 应用退出 |
onMemoryLevel |
内存压力变化 | 丢缓存、缩图片内存 | 少见,但原生很关键 |
本仓库已有的钩子骨架:
7:52:entry/src/main/ets/entryability/EntryAbility.ets
export default class EntryAbility extends UIAbility {
onCreate(...) { ... }
onDestroy() { ... }
onWindowStageCreate(windowStage) { ... loadContent ... }
onWindowStageDestroy() { ... }
onForeground() { ... }
onBackground() { ... }
}
3.3 页面级生命周期(别和 Ability 混)
页面组件还有自己的约生命周期(如 aboutToAppear / aboutToDisappear 等),对应 Vue 的 onMounted / onUnmounted。
分层原则:
- App 级(登录态校验、前后台、全局字体)→ Ability
- 页面级(请求列表、解绑监听)→ 页面组件生命周期
- 组件级(输入框焦点、局部动画)→ 组件自身
四、模块化:工程怎么拆,对应 Web 什么
鸿蒙应用按 Module 组织,产物最终打进 HAP / HSP 等包。常见类型:
| 模块类型 | 角色 | 前端类比 |
|---|---|---|
| entry | 主入口模块,含主 Ability | 主应用 apps/web |
| feature | 可独立安装的功能模块 | 业务分包 / 微前端子应用 |
| HAR | 静态共享包(源码/资源复用) | 内部 npm 组件库 |
| HSP | 动态共享包(运行时可共享) | 运行时共享依赖 / DLL 思路 |
本仓库当前是典型的单 entry:
1:6:entry/src/main/module.json5
{
"module": {
"name": "entry",
"type": "entry",
"mainElement": "EntryAbility",
页面清单在 main_pages.json 注册------没注册的页面,路由跳不过去 (类似没配进 vue-router 的路由表):
1:7:entry/src/main/resources/base/profile/main_pages.json
{
"src": [
"pages/Index",
"pages/login",
"pages/Main/home"
]
}
模块化落地建议(给前端同学):
- 先单 entry 跑通业务,再拆 feature / HAR。
- 公共 UI、工具、model → HAR;大功能域(商城、消息)再考虑 feature。
- 配置(
module.json5、pages、权限)和代码目录一起改,否则「文件有了、系统不认」。
五、路由与导航:两套体系,别混用场景
鸿蒙日常会遇到两种「跳转」:
5.1 页面路由 router(页面级,偏 vue-router)
适用于:登录 → 首页、设置页、协议页等整页切换。
常见 API 心智:
| API 思路 | 含义 | Vue Router |
|---|---|---|
pushUrl |
入栈打开新页面 | router.push |
replaceUrl |
替换当前页(无返回) | router.replace |
back |
返回 | router.back |
| 传参 | params / 启动参数 | query / params |
本仓库登录成功注释里已预留:
ts
// 登录成功后跳转:router.replaceUrl({ url: 'pages/Main/home' });
注意:
url必须能在main_pages.json里找到对应 path。- 登录成功常用
replaceUrl,避免返回键又回到登录页(和 Vue 里replace: true一样)。 - 需要带复杂对象时,优先用明确字段 / 状态层,而不是塞巨型 JSON。
5.2 Navigation + NavPathStack(组件级,偏 App 内多栈)
适用于:首页 Tab 内再推详情、个人中心里多层子页------一个 Ability 内的导航栈。
本仓库主页结构就是典型「多 Tab × 多栈」:
8:56:entry/src/main/ets/pages/Main/main.ets
@State currentTab: number = 0;
private homeStack: NavPathStack = new NavPathStack();
private profileStack: NavPathStack = new NavPathStack();
...
Tabs({ barPosition: BarPosition.End, index: this.currentTab }) {
TabContent() {
Navigation(this.homeStack) {
HomePage()
}
.mode(NavigationMode.Stack)
}
...
TabContent() {
Navigation(this.profileStack) {
ProfilePage()
}
.mode(NavigationMode.Stack)
}
}
对前端的映射:
| 场景 | Vue 常见写法 | 鸿蒙推荐 |
|---|---|---|
| 登录进首页 | replace('/home') |
router.replaceUrl |
| Tab 切换 | 嵌套路由 / keep-alive | Tabs + 各自 NavPathStack |
| Tab 内进详情 | children 路由 |
homeStack.pushPath(...) |
| 跨 Ability 拉起 | 几乎没有 | Want 启动另一个 Ability |
更多路由细节与踩坑,可对照社区实践:路由导航相关博文。
5.3 选型口诀
换「整页入口」用
router;在「当前业务流里往下钻」用Navigation。Tab 场景优先「一 Tab 一栈」,返回体验才像原生 App。
六、和 Vue 生命周期 / 路由的一张总对照表
| 你在 Vue 里做的事 | 鸿蒙落点 |
|---|---|
main.ts 创建应用 |
UIAbility.onCreate + onWindowStageCreate |
App.vue 根布局 |
loadContent 加载的首个 @Entry 页面 |
router.push/replace |
router.pushUrl/replaceUrl |
| 嵌套路由 / 多级详情 | Navigation + NavPathStack |
onMounted / onUnmounted |
页面 aboutToAppear / aboutToDisappear 等 |
| 监听页面可见性 / 前后台 | Ability onForeground / onBackground |
| 微前端 / monorepo 分包 | feature 模块 + HAR/HSP |
| 环境变量 / 全局插件 | Ability 初始化 + AppStorage 等 |
七、实战建议(按本仓库可立刻做的)
- 理清启动链 :桌面图标 →
EntryAbility→loadContent('pages/Index')→ 再决定是否进login/Main。 - 登录跳转用
replaceUrl,并保证目标页已写入main_pages.json。 - 主框架用 Tabs + 双
NavPathStack(你已具备),详情页往对应 stack 推,而不是到处router.push。 - 在 Ability 的
onBackground停掉轮询 / 动画,避免后台耗电(Stage 模型的核心价值之一)。 - 模块先不急着拆:业务稳定后再抽 HAR;过早拆模块会同时踩配置与依赖两个坑。
八、本篇结论
- Stage 解决的是「应用在系统里处于哪一阶段、资源怎么管」。
- Ability 解决的是「谁被系统拉起、窗口与全局生命周期放哪」。
- 模块化 解决的是「工程如何拆包复用」,对应 Web 的应用 / 分包 / 组件库。
- 路由 分两层:
router管页面,Navigation管 Ability 内导航栈。
前端转鸿蒙,先把这四层叠起来,后面写业务页才不会「路由乱跳、后台还在请求、页面注册了却进不去」。