鸿蒙项目首页启动链路与 ArkUI 架构学习总结

鸿蒙项目首页启动链路与 ArkUI 架构学习总结

日期:2026-05-26

主题:公司鸿蒙项目启动入口、Ability 生命周期、HMRouter 首页承接、ArkUI 状态管理 V2、首页分层架构理解

一、今天主要解决了什么问题

今天的重点不是继续写聊天 demo,而是回到公司真实项目里,先把应用是怎么启动、页面是怎么被加载、首页是怎么被路由找到的这条主线理清楚。

一开始看项目的时候,代码量比较大,页面、模块、路由、业务层、组件层都分散在不同目录里。如果直接看首页 UI,很容易只看到一堆组件和业务代码,但是不知道它们到底是从哪里被启动起来的。

所以今天先从项目入口开始拆,最终确认了当前项目的启动链路:

text 复制代码
entry/src/main/module.json5
  ↓
EntryAbility.ets
  ↓
onCreate()
  ↓
onWindowStageCreate()
  ↓
windowStage.loadContent('pages/MainPage')
  ↓
entry/src/main/ets/pages/MainPage.ets
  ↓
HMNavigation
  ↓
homePageUrl: LushuPageConstant.HomePage
  ↓
yuanzhou_home/src/main/ets/pages/MainTabPage.ets
  ↓
首页 Tab 组件

也就是说,module.json5 里配置的 srcEntry 只是找到真正的 Ability 入口,真正把页面加载出来的是 EntryAbility 里的 onWindowStageCreate(),再通过 windowStage.loadContent('pages/MainPage') 进入主页面。之后 MainPage.ets 里面不是直接写首页 UI,而是通过 HMNavigationhomePageUrl 交给 HMRouter 去找真实首页。

今天已经把这个链路跑通了,并且通过替换首页组件,让首页显示出了一个简单的"你好"。虽然 UI 很简单,但是这个结果很关键,因为它证明了:

text 复制代码
1. 应用入口没有找错;
2. EntryAbility 能正常加载 MainPage;
3. MainPage 里的 HMNavigation 是生效的;
4. LushuPageConstant.HomePage 对应的首页路由是有效的;
5. 自己替换进去的新首页组件已经成功接管首页位置。

这一步跑通之后,后面不管是还原首页,还是做公司真实需求,都不会再停留在"这个页面到底从哪里来"的阶段。


二、module.json5 和 srcEntry 的作用

在 HarmonyOS 项目里,module.json5 是模块配置文件。今天重点关注的是里面的 srcEntry

json 复制代码
{
  "module": {
    "srcEntry": "./ets/entryability/EntryAbility.ets"
  }
}

这行配置的意思是:当前模块真正的 Ability 入口文件是:

text 复制代码
entry/src/main/ets/entryability/EntryAbility.ets

以前容易误解为页面入口就是某个 pages/Index.ets,但公司项目不是这样。真实启动流程是先启动 Ability,再由 Ability 创建窗口,再加载页面。

可以理解成:

text 复制代码
module.json5 负责告诉系统:入口 Ability 在哪里
EntryAbility 负责告诉系统:窗口创建后加载哪个页面
MainPage 负责告诉路由容器:默认首页路由是哪一个
HMRouter 负责根据 pageUrl 找到真正的业务页面

这个理解很重要,因为以后遇到任何鸿蒙项目,不能一上来就找 @Entry 页面,而是要先看 module.json5 和 Ability 配置。


三、EntryAbility 生命周期理解

今天重点看了 EntryAbility.ets,核心关注两个生命周期方法:

ts 复制代码
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // Ability 创建时执行
}

onWindowStageCreate(windowStage: window.WindowStage): void {
  // WindowStage 创建时执行
  windowStage.loadContent('pages/MainPage')
}

结合华为官方文档的描述,UIAbility 启动时系统会依次触发 onCreate()onWindowStageCreate()onForeground() 等生命周期回调。onCreate() 更偏向 Ability 初始化阶段,onWindowStageCreate() 则是在窗口舞台创建完成之后执行,一般会在这里加载入口页面。

今天在公司项目里看到的情况也正好对应这个过程:

text 复制代码
onCreate()
  - 初始化全局配置
  - 初始化登录态
  - 初始化缓存、主题、网络
  - 初始化 HMRouterMgr
  - 初始化埋点、隐私、卡片等全局能力

onWindowStageCreate()
  - 初始化窗口
  - 设置窗口相关配置
  - 初始化页面转场动画
  - windowStage.loadContent('pages/MainPage')

这里要注意一个细节:

onCreate() 并不直接显示 UI,它更像是应用启动时的全局初始化入口。真正把页面挂到窗口上的是 onWindowStageCreate()

所以以后分析启动问题,可以按这个顺序排查:

text 复制代码
1. module.json5 的 srcEntry 是否正确;
2. EntryAbility 是否正常执行 onCreate;
3. onWindowStageCreate 是否执行;
4. loadContent 的页面路径是否正确;
5. 被加载的 MainPage 是否有 @Entry;
6. MainPage 内部的路由容器是否正确配置首页路由。

四、MainPage 为什么不是直接首页

项目里 EntryAbility 加载的是:

ts 复制代码
windowStage.loadContent('pages/MainPage')

从名字看,容易以为 MainPage 就是首页 UI。但实际看下来,MainPage 更像是一个应用级导航容器页面

它里面核心不是首页业务,而是类似这样的结构:

ts 复制代码
@Entry
@ComponentV2
struct MainPage {
  build() {
    HMNavigation({
      homePageUrl: LushuPageConstant.HomePage
    })
  }
}

这里的关键是:

text 复制代码
MainPage 只是入口容器;
首页真实内容由 homePageUrl 决定;
HMNavigation 会根据 pageUrl 找到被 @HMRouter 注册的页面。

也就是说,MainPage 不直接关心首页长什么样,它只告诉路由框架:

text 复制代码
默认首页是 LushuPageConstant.HomePage

然后真正的首页页面,是 yuanzhou_home/src/main/ets/pages/MainTabPage.ets 通过 @HMRouter 注册上的。

这就是公司项目和普通 demo 最大的区别:

普通 demo 可能是:

text 复制代码
EntryAbility -> Index.ets -> UI

公司项目是:

text 复制代码
EntryAbility -> MainPage -> HMNavigation -> HMRouter -> 业务模块页面

这种方式更适合大型项目,因为页面分布在不同模块里,不能靠一个固定的 pages/Index 管全部页面。


五、HMRouter 首页承接关系

首页路由的核心是:

ts 复制代码
@HMRouter({ pageUrl: LushuPageConstant.HomePage, singleton: true })
@ComponentV2
export struct MainTabPage {
  build() {
    // Tabs 首页容器
  }
}

LushuPageConstant.HomePage 对应的是一个固定的页面地址,例如:

text 复制代码
page://Lushu/HomePage

整个过程可以理解成:

text 复制代码
HMNavigation
  ↓
homePageUrl: page://Lushu/HomePage
  ↓
HMRouter 路由表中查找
  ↓
找到 @HMRouter({ pageUrl: page://Lushu/HomePage }) 注册的页面
  ↓
渲染 MainTabPage

今天实际操作里,我把原来首页 Tab 中的内容替换成了自己的新组件,然后页面成功显示"你好"。这个结果说明:

text 复制代码
MainTabPage 的路由注册是生效的;
MainPage 的 homePageUrl 没有问题;
自定义组件能被 MainTabPage 正常挂载。

这里也踩到了一个关键点:

同一个 pageUrl 不要同时注册两个页面。

如果原来的 MainTabPage 已经注册了:

ts 复制代码
@HMRouter({ pageUrl: LushuPageConstant.HomePage })

就不要再新建一个页面也注册同一个 pageUrl,否则可能出现路由冲突,或者框架只识别其中一个,结果不稳定。

更稳的做法是:

text 复制代码
保留原来的 MainTabPage 路由注册;
只替换 MainTabPage 里首页 TabContent 的组件;
用自己的 DemoTabHomeComp 接管首页区域。

这样既不会破坏启动链路,也方便回退。


六、为什么今天只显示"你好"也是有价值的

今天最后页面只显示了一个"你好",但这不是无效工作。

因为在一个大型项目里,最难的第一步不是写 UI,而是先确认自己写的东西能不能进入真实页面链路。

"你好"跑通,代表下面这条链路已经完全打通:

text 复制代码
module.json5
  ↓
EntryAbility
  ↓
MainPage
  ↓
HMNavigation
  ↓
LushuPageConstant.HomePage
  ↓
MainTabPage
  ↓
自定义首页组件
  ↓
屏幕显示

后面无论是写首页 UI,还是做真实需求,都可以复用这个思路:

text 复制代码
先找入口
再找路由
再找页面
再找组件
再找 ViewModel / Controller / Biz
最后再改 UI 或业务逻辑

这个思路比直接在项目里乱搜页面名更靠谱。


七、ArkUI 声明式 UI 的理解

HarmonyOS 使用 ArkUI 声明式 UI 开发框架。官方文档里对 ArkUI 的定位是:它是一套用于构建应用界面的声明式 UI 框架,提供简洁的 UI 语法、丰富的 UI 组件以及预览能力。

今天实际看到的页面代码基本都是这种形式:

ts 复制代码
@ComponentV2
export struct DemoTabHomeComp {
  build() {
    Column() {
      Text('你好')
        .fontSize(20)
        .fontColor(Color.Black)
    }
    .width('100%')
    .height('100%')
  }
}

这种写法和传统命令式 UI 不一样。它不是一步步调用 API 去创建 View、设置属性、添加到父容器,而是在 build() 里声明页面结构:

text 复制代码
Column 里面有什么;
Text 显示什么;
组件宽高是多少;
字体颜色是多少;
点击事件绑定什么函数。

可以理解为:

text 复制代码
开发者描述 UI 应该长什么样;
ArkUI 框架根据状态变化自动重新渲染对应区域。

今天用到或者看到比较多的组件有:

text 复制代码
Column  纵向布局
Row     横向布局
Stack   层叠布局
Scroll  滚动容器
Tabs    标签页容器
Text    文本组件
Image   图片组件
Grid    网格组件
ForEach 列表渲染

这些组件基本构成了首页 UI 的基础。


八、@Entry、@ComponentV2、build 的关系

今天看到的主页面里有 @Entry,业务组件里更多是 @ComponentV2

可以这样理解:

text 复制代码
@Entry
表示这个自定义组件是页面入口组件,一个页面文件通常只有一个 @Entry。

@ComponentV2
表示这是一个 ArkUI 自定义组件,可以被其他组件引用和组合。

build()
是组件的 UI 描述函数,页面最终显示什么,主要由 build 返回的组件树决定。

例如:

ts 复制代码
@Entry
@ComponentV2
struct MainPage {
  build() {
    HMNavigation({
      homePageUrl: LushuPageConstant.HomePage
    })
  }
}

这里 MainPage 是被 windowStage.loadContent('pages/MainPage') 加载出来的入口页面,所以它需要 @Entry

而业务首页组件类似:

ts 复制代码
@ComponentV2
export struct DemoTabHomeComp {
  build() {
    Text('你好')
  }
}

它本身不是页面入口,只是被 MainTabPage 组合进去,所以不需要 @Entry

今天一个重要收获是:

text 复制代码
不是每个能显示 UI 的文件都需要 @Entry;
只有被 loadContent 作为页面入口加载的页面才需要 @Entry;
普通业务组件用 @ComponentV2 即可。

九、@Builder 的作用

公司项目里的页面通常不会把所有 UI 都堆在 build() 里面,而是大量使用 @Builder 拆分 UI。

例如首页可以拆成:

ts 复制代码
build() {
  Stack() {
    this.topBgBuilder()
    this.contentBuilder()
  }
}

@Builder
topBgBuilder() {
  Column()
    .height(220)
    .backgroundColor('#4B8DFF')
}

@Builder
contentBuilder() {
  Scroll() {
    Column() {
      this.headerBuilder()
      this.searchBuilder()
      this.serviceBuilder()
      this.hotRouteBuilder()
    }
  }
}

@Builder 的价值不是复杂,而是让页面结构更清楚。

如果所有 UI 都写在 build() 中,首页这种复杂页面会很快变成几百行嵌套代码,不好维护。拆成 Builder 后,可以按模块读代码:

text 复制代码
topBgBuilder      顶部背景
headerBuilder     顶部定位栏
searchBuilder     搜索框
serviceBuilder    常用服务
hotRouteBuilder   热门路线

公司项目里的首页也是类似思路,大组件负责拼装,小 Builder 负责局部 UI。


十、状态管理 V2:@ObservedV2 和 @Trace

今天项目里重点看到的是状态管理 V2,尤其是:

ts 复制代码
@ObservedV2
export class TabHomeViewModel {
  @Trace recommendTabs: RecommendTabInfo[] = []
  @Trace mustPlay: MustPlayList[] = []
  @Trace currentFirstLevelId: string = ''
}

官方文档里有一个关键点:@ObservedV2 本身只是表示这个 class 可以被观察,它自己没有属性级观察能力;真正让属性变化能被 UI 感知的是 @Trace

所以可以这样记:

text 复制代码
@ObservedV2 标记这个类是可观察对象;
@Trace 标记这个类里的某个字段需要被观察;
字段变化后,绑定这个字段的 UI 会刷新。

错误理解是:

text 复制代码
只要 class 加了 @ObservedV2,里面所有字段都会自动刷新 UI。

正确理解是:

text 复制代码
class 加 @ObservedV2 只是前提;
真正需要刷新的字段还要加 @Trace。

例如:

ts 复制代码
@ObservedV2
export class DemoHomeViewModel {
  @Trace locationName: string = '中国香港'
  @Trace serviceList: string[] = []
}

UI 中使用:

ts 复制代码
Text(this.viewModel.locationName)

ForEach(this.viewModel.serviceList, (item: string) => {
  Text(item)
})

当 Controller 里修改:

ts 复制代码
this.viewModel.locationName = '深圳'
this.viewModel.serviceList = ['机票', '酒店', '火车票']

绑定了这些字段的 UI 就会自动更新。


十一、为什么数组最好重新赋值

今天结合之前聊天 demo 的经验,也再次确认了一个点:数组更新时,最好用"新数组赋值"的方式触发刷新。

比如不推荐只这样写:

ts 复制代码
this.viewModel.serviceList.push({ title: '更多' })

更稳的方式是:

ts 复制代码
this.viewModel.serviceList = this.viewModel.serviceList.concat([{ title: '更多' }])

或者接口返回后直接整体赋值:

ts 复制代码
this.viewModel.serviceList = data.serviceList

这样做的原因是:

text 复制代码
状态管理关注的是被观察字段的变化;
直接重新给 @Trace 字段赋值,更容易触发依赖这个字段的 UI 更新;
复杂对象或数组内部原地修改,有时候不如整体替换直观稳定。

所以后面做首页数据接入时,建议保持这个风格:

ts 复制代码
this.viewModel.recommendTabs = result.recommendTabs ?? []
this.viewModel.hotRouteList = result.hotRouteList ?? []
this.viewModel.discoveryList = result.discoveryList ?? []

不要在 UI 层或者 Controller 里大量 push


十二、@Local 的理解

公司项目里组件内部经常能看到:

ts 复制代码
@Local viewModel: TabHomeViewModel = new TabHomeViewModel()

@Local 可以理解为组件内部自己的状态变量。它通常适合这种场景:

text 复制代码
这个状态只属于当前组件;
不需要父组件传入;
不需要向外同步;
组件自己创建、自己使用。

在今天的首页替换里,也可以这样写:

ts 复制代码
@ComponentV2
export struct DemoTabHomeComp {
  @Local viewModel: DemoHomeViewModel = new DemoHomeViewModel()
  controller: DemoHomeController = new DemoHomeController(this.viewModel)
}

这里 viewModel 是当前首页组件自己的状态中心,所以用 @Local 比较合适。

如果某个字段是父组件传进来的,比如当前 Tab 下标,就更可能用 @Param 之类的方式,而不是组件内部自己 new。


十三、V1 和 V2 状态装饰器不能乱混用

今天看项目时要特别注意一个坑:状态管理有 V1 和 V2 两套装饰器。

常见 V1:

text 复制代码
@State
@Prop
@Link
@Observed
@ObjectLink
@Track

常见 V2:

text 复制代码
@ComponentV2
@Local
@Param
@ObservedV2
@Trace
@Monitor

官方文档也强调了 V1 和 V2 的混用限制。简单记就是:

text 复制代码
@ComponentV2 里优先使用 V2 装饰器;
@ObservedV2 搭配 @Trace;
不要在 @ObservedV2 里混用 @Track;
不要把 V1 的观察模型和 V2 的观察模型随便混在一起。

项目里如果已经是:

ts 复制代码
@ComponentV2
export struct TabHomeComp {}

那么 ViewModel 就应该优先使用:

ts 复制代码
@ObservedV2
export class TabHomeViewModel {
  @Trace xxx: string = ''
}

不要写成:

ts 复制代码
@Observed
export class TabHomeViewModel {
  @Track xxx: string = ''
}

否则后面可能出现编译问题、刷新问题或者状态观察不符合预期。


十四、公司项目首页的分层理解

今天除了启动链路,另一个重点是理解首页的分层。

公司项目不是把 UI、请求、点击事件、数据结构都写在一个页面里,而是拆成了类似这样的结构:

text 复制代码
pages
  MainTabPage.ets

components
  TabHomeComp.ets

controller
  TabHomeController.ets

viewmodel
  TabHomeViewModel.ets

biz
  HomeBiz.ets

model
  TabHomeModel.ets

每一层职责不同:

text 复制代码
Page 层
负责页面承接、路由注册、Tabs 容器。

Component 层
负责 UI 展示,用 @Builder 拆分页面结构。

ViewModel 层
负责页面状态,用 @ObservedV2 和 @Trace 保存可刷新数据。

Controller / Presenter 层
负责生命周期、点击事件、接口调度、路由跳转、埋点等页面逻辑。

Biz 层
负责业务请求封装,屏蔽具体接口细节。

Model 层
负责类型定义、接口返回结构、页面数据结构。

这套结构的好处是:

text 复制代码
UI 不直接请求接口;
接口不直接改页面;
点击事件不散落在 UI 里;
状态集中在 ViewModel;
业务逻辑集中在 Controller;
数据类型集中在 Model。

十五、首页数据流应该怎么走

今天梳理出的首页数据流是:

text 复制代码
TabHomeComp.aboutToAppear()
  ↓
TabHomeController.aboutToAppear()
  ↓
reloadData()
  ↓
HomeBiz.getTabHomeData(param)
  ↓
接口返回首页数据
  ↓
Controller 解析并赋值 ViewModel
  ↓
ViewModel 中 @Trace 字段变化
  ↓
UI 自动刷新

仿写的时候,可以先做一个缩小版:

text 复制代码
DemoTabHomeComp.aboutToAppear()
  ↓
DemoHomeController.aboutToAppear()
  ↓
DemoHomeController.reloadData()
  ↓
DemoHomeBiz.getHomeData()
  ↓
返回 mock 数据
  ↓
this.viewModel.serviceList = data.serviceList
  ↓
页面刷新

对应代码大概是:

ts 复制代码
export class DemoHomeController {
  private viewModel: DemoHomeViewModel
  private biz: DemoHomeBiz = new DemoHomeBiz()

  constructor(viewModel: DemoHomeViewModel) {
    this.viewModel = viewModel
  }

  async aboutToAppear(): Promise<void> {
    await this.reloadData()
  }

  async reloadData(): Promise<void> {
    const data = await this.biz.getHomeData()
    this.viewModel.locationName = data.locationName
    this.viewModel.serviceList = data.serviceList
    this.viewModel.hotRouteList = data.hotRouteList
  }
}

这个结构很适合后面接真实需求。


十六、为什么 UI 不能直接调接口

如果在 UI 组件里直接写:

ts 复制代码
aboutToAppear() {
  Api.get(...).then((res) => {
    this.serviceList = res.data
  })
}

短期看也能跑,但问题很多:

text 复制代码
1. 页面越来越大,UI 和业务逻辑混在一起;
2. 接口参数散落在多个组件里;
3. 错误处理、loading、重试逻辑不好统一;
4. 后面换接口或加埋点,需要到 UI 里到处改;
5. 不符合公司项目已有架构。

更合理的写法是:

text 复制代码
UI:只负责展示和绑定点击事件;
Controller:负责收到点击后做什么;
Biz:负责请求哪个业务接口;
ViewModel:负责保存最后给 UI 用的数据。

所以首页组件里最好只保留这种调用:

ts 复制代码
async aboutToAppear(): Promise<void> {
  await this.controller.aboutToAppear()
}

.onClick(() => {
  this.controller.onClickSearch()
})

真正的请求、判断、路由跳转都放进 Controller。


十七、今天实践出来的替换策略

一开始有两种替换首页的方案:

text 复制代码
方案一:新建页面,注册同一个 HomePage 路由,替换原 MainTabPage。
方案二:保留 MainTabPage,只替换里面的首页 Tab 组件。

今天更推荐方案二,因为它更稳:

text 复制代码
保留原路由;
保留 MainTabPage;
保留底部 Tabs;
只替换首页内容组件;
出问题时容易恢复。

实际操作可以理解成:

text 复制代码
原来:
LushuPageConstant.HomePage
  ↓
MainTabPage
  ↓
TabHomeComp

现在:
LushuPageConstant.HomePage
  ↓
MainTabPage
  ↓
DemoTabHomeComp

这样既验证了首页挂载链路,也没有破坏整个应用入口。


十八、后面做真实需求时的分析模板

今天这套思路后面可以直接复用。以后接到需求,不要上来就改代码,先按下面模板分析:

text 复制代码
1. 这个需求影响哪个页面?
2. 这个页面的 pageUrl 是什么?
3. 页面是由 @Entry 加载,还是由 @HMRouter 注册?
4. 这个页面在哪个模块?
5. 页面入口文件在哪里?
6. 页面 UI 组件在哪里?
7. ViewModel 是哪个?
8. Controller / Presenter 是哪个?
9. Biz 或接口封装在哪里?
10. 数据来源是接口、缓存、本地 mock,还是上个页面传参?
11. 是否涉及登录态、隐私协议、地区、会员、埋点?
12. 修改点是 UI、交互、接口、状态,还是路由?

这样能避免盲目搜索和乱改。


十九、今天学到的关键知识点总结

今天学到的内容可以压缩成下面几句话:

text 复制代码
1. module.json5 通过 srcEntry 指定真正的 Ability 入口。
2. EntryAbility 的 onCreate 负责全局初始化,不直接显示页面。
3. onWindowStageCreate 负责窗口创建后的页面加载。
4. windowStage.loadContent('pages/MainPage') 加载的是应用导航容器。
5. MainPage 通过 HMNavigation 和 homePageUrl 指向真实首页路由。
6. 首页真实页面由 @HMRouter({ pageUrl }) 注册。
7. MainTabPage 是首页 Tabs 容器,不一定直接写首页业务 UI。
8. 替换首页时,优先替换 TabContent 里的组件,而不是重复注册同一个路由。
9. ArkUI 是声明式 UI,build() 用来描述组件树。
10. @Entry 是页面入口组件,普通业务组件不需要加。
11. @ComponentV2 用来声明 V2 自定义组件。
12. @Builder 用来拆分复杂 UI,提升页面可读性。
13. @ObservedV2 标记 class 可观察,但本身不观察字段。
14. @Trace 才是让字段变化触发 UI 更新的关键。
15. 数组更新时,整体赋值比原地 push 更稳定。
16. V1 和 V2 状态装饰器不要乱混用。
17. 公司项目页面要按 Page / Component / ViewModel / Controller / Biz / Model 分层理解。
18. UI 不直接请求接口,Controller 调 Biz,Biz 返回数据,Controller 更新 ViewModel,UI 自动刷新。

二十、后续可以继续做什么

目前首页替换已经跑通,后续如果继续练首页,可以按这个顺序做:

text 复制代码
第一步:把"你好"替换成首页基础布局
  - Stack
  - 顶部背景
  - Scroll
  - Column 内容区

第二步:抽 ViewModel
  - locationName
  - searchPlaceholder
  - serviceList
  - hotRouteList

第三步:抽 Controller
  - aboutToAppear
  - reloadData
  - onClickSearch
  - onClickService

第四步:抽 Biz
  - getHomeData
  - 先返回 mock 数据

第五步:UI 绑定 ViewModel
  - ForEach 服务列表
  - ForEach 热门路线

第六步:再做 UI 还原
  - 背景图
  - 搜索框
  - 服务图标
  - 热门路线卡片
  - 发现列表

第七步:最后再接真实接口和路由跳转

如果接到的是公司真实需求,就不需要继续为了练习完整复原首页,而是把今天学到的方法用到真实需求里:

text 复制代码
找入口 -> 找路由 -> 找页面 -> 找组件 -> 找 ViewModel -> 找 Controller -> 找 Biz -> 最小范围修改

参考文档

  1. 华为开发者文档:UIAbility 组件生命周期
    developer.huawei.com/consumer/cn...

  2. 华为开发者文档:ArkUI 介绍
    developer.huawei.com/consumer/cn...

  3. 华为开发者文档:状态管理 V2 版本
    developer.huawei.com/consumer/cn...

  4. 华为开发者文档:状态管理 V1 和 V2 混用指导
    developer.huawei.com/consumer/cn...

  5. 华为开发者文档:@Monitor 状态变量修改监听
    developer.huawei.com/consumer/cn...


最后总结

今天最大的收获不是写出了多少页面,而是把公司鸿蒙项目的主线摸清楚了。

以前看页面时,可能只知道某个 .ets 文件能显示 UI,但不知道它是怎么被启动、怎么被路由找到、怎么和 Ability、MainPage、HMNavigation 连接起来的。今天通过入口分析和首页替换,已经确认了从 module.json5 到真实首页组件的完整链路。

同时也进一步理解了公司项目为什么要拆分 ViewModel、Controller、Biz 和 Model。大型项目里,页面不能只靠一个组件文件硬写,必须把 UI、状态、业务、请求、类型定义拆开,否则后面需求一多就很难维护。

后续做真实需求时,可以继续沿用今天这套分析方法:先把链路看明白,再按架构改代码,不直接在 UI 里堆逻辑。

相关推荐
ssshooter9 小时前
Tauri 应用首次上架 App Store 被驳回了 3 次(iOS)和 12 轮(macOS)的经历
前端·ios·程序员
阿祖zu9 小时前
2026 企业级 Agent 产品落地思考与全流程指南
前端·程序员·aigc
yqcoder9 小时前
拆解互联网:通俗易懂的网络分层模型
前端·网络·网络协议
小鹿软件办公9 小时前
Mozilla 解释 Firefox 在英特尔 Raptor Lake 系统上的崩溃问题
前端·firefox
代码熊崽的编程森林9 小时前
vue + onlyoffice 自定义插件的实现(OnlyOffice 插件:AI 智能编辑)。
前端·javascript·vue.js
Lucky_Turtle10 小时前
【Vue】element plus Slider小数组件设置顺滑程度
前端·javascript·vue.js
Bigger10 小时前
🔥 一份 Agent 工程岗 JD,暴露了市场真正想要什么样的人
前端·agent·全栈
我头上有犄角ovo10 小时前
我在微信小程序里手搓人脸识别引导,结果被“右转头”和“手遮脸”教育了
前端
David_Xia10 小时前
干爆 11s 提交卡顿!引入 Rust 级 oxlint 彻底拯救团队 Git Commit 噩梦的重构实践
前端