为啥还分不清 Stage 和 Page?不如一小时把 ArkUI 玩明白,岂不快哉?

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
前言
老实说,第一次打开鸿蒙应用项目时,我也被“Stage 模型”“Page 模型”整得一愣一愣的:这俩到底谁管窗、谁管页、谁来调度生命周期?别急,今天我们用人话 + 代码把鸿蒙应用框架(ArkUI)扒个明白:从模型差异、UI 组件体系,到状态管理与动画——不堆概念,不玩花活,写出能跑、能上线、能扩展的代码。要点清晰、坑点直说,顺手塞点我亲测的“避坑锦囊”。走起~🚀
1) ArkUI 是啥:更像“声明式搭积木”,而不是“命令式画图纸”
ArkUI 是鸿蒙(OpenHarmony/HarmonyOS)的声明式 UI 框架,语法糖来自 ArkTS(TypeScript 的超集语法)。口号拆解一下:
- 声明式:你描述“界面应该长啥样”,框架负责“怎么画出来”。
- 响应式:数据变了,界面自动刷新(无需手搓
findViewById那套)。 - 跨端设计:组件语义稳定,设备差异由框架适配(手机、平板、可穿戴…)。
一句话:像 React 的写法 + 原生系统级性能,但保留了鸿蒙生态的一体化能力。
2) Stage 模型 vs Page 模型:到底该怎么选?
这俩模型不是“新旧替代”,而是“视角不同”:
Page 模型(轻量、上手快)
- 面向页面栈:以页面(Page)为单位,配合路由跳转管理视图。
- 简化生命周期:少碰底层窗口、任务栈,写业务最省心。
- 适合:中小型 App、单窗体、以页面堆栈为主的交互。
记忆法:Page 就是“页面导向”。像纯前端路由一样丝滑。
Stage 模型(灵活、工程化)
- 面向能力(Ability)和窗口(WindowStage):能精细控制窗口、任务、多实例。
- 生命周期全面:Ability 启动/挂起/恢复/销毁全流程可控。
- 适合:多窗口、跨任务、复杂系统集成、需精细化生命周期管理的中大型 App。
记忆法:Stage 就是“舞台导向”。灯光、幕布、演员(窗口、任务、页面)都你说了算。
选型建议
- 80% 业务:用 Page 模型,快。
- 需要多窗口/系统级协作:上 Stage 模型,稳。
- 团队分层明确:UI 团队 Page、平台组 Stage,不冲突还能共存。
3) UI 组件体系:Row/Column/Stack 只是开胃菜,List/Grid/Tabs 才是主菜
ArkUI 的组件分三类理解更顺:
- 布局容器:
Row、Column、Stack、Flex、Grid - 基础控件:
Text、Image、Button、TextInput、Checkbox、Switch、Slider - 结构与导航:
Navigation/NavDestination、Tabs、List/ListItem、Scroll、Search
特点:属性链式 + 修饰器式样式,写起来“像拼句子”。
示意(ArkTS):
@Entry
@Component
struct HomePage {
@State keyword: string = ''
build() {
Column({ space: 12 }) {
Text('ArkUI 入门清单').fontSize(22).fontWeight(FontWeight.Bold).padding(12)
TextInput({ placeholder: '搜索点什么……' })
.width('100%')
.onChange(v => this.keyword = v)
List() {
ListItem() { Text('Stage vs Page,懂了没?') }
ListItem() { Text('状态管理——@State 到 @Provide/@Consume') }
ListItem() { Text('动画系统——隐式与显式的取舍') }
}
.width('100%')
.divider({ strokeWidth: 1 })
}
.padding(16)
}
}
4) 状态管理:@State 到 @Provide/@Consume,一步步把“数据这口锅”端稳
ArkUI 的状态系统分层很清晰,理解“数据归属”就不乱:
- 局部状态:
@State(组件私有)、@Link(对子组件双向绑定)、@Prop(父传子只读) - 跨层共享:
@Provide/@Consume(祖先-后代依赖注入式共享) - 应用级存储:
AppStorage/@StorageLink(全局可持久/可观察) - 可观察对象:
@Observed/@ObjectLink(对象属性级变更跟踪,适合复杂模型)
小抄一张表:
| 想解决的问题 | 用什么 | 说明 |
|---|---|---|
| 组件内部的 UI 状态 | @State |
最常用,最简单 |
| 父组件把只读数据给子组件 | @Prop |
子组件不能改 |
| 父子双向同步(小心滥用) | @Link |
小范围联动很香 |
| 跨很多层共享(如主题/用户) | @Provide/@Consume |
结构化依赖注入 |
| 全局共享 + 可观察 | AppStorage/@StorageLink |
持久、跨页面 |
| 复杂对象变更 | @Observed/@ObjectLink |
局部字段变更也能触发 |
示例:@Provide/@Consume 共享主题色
@Provide('themeColor') themeColor: Color = Color.Blue
// 任意后代
@Consume('themeColor') color!: Color
5) 动画系统:隐式动画到显式动画,从“丝滑过渡”到“情绪表达”
ArkUI 动画大体两路:
-
隐式动画(推荐兜底)
在样式链上加.animation(),样式变化自动平滑过渡。写最少,观感友好。@State progress: number = 0 Rectangle() .width(this.progress + '%') .height(6) .borderRadius(3) .animation({ duration: 300, curve: Curve.EaseInOut }) -
显式动画(需要编排时)
用animateTo()、TransitionEffect、KeyframeAnimation等,精确控制时序、曲线、联动。animateTo({ duration: 400, curve: Curve.Spring }) => { this.open = !this.open this.opacity = this.open ? 1 : 0 this.offsetY = this.open ? 0 : -20 }
补两味“调料”:
- 过渡(transition):
TransitionEffect做进出场(Fade/Slide/Scale/Shared),路由切页更自然。 - 手势 + 动画:
Gesture与PanGesture联动animateTo,做“拖拽回弹”那味儿人性化交互。
6) 一套可跑的微型示例:待办清单 + 过渡动画(ArkTS)
目标:展示 Page 模型 下的页面路由、状态管理(列表 + 局部编辑)、动画(新增项的“气泡”过渡)。
路由入口(Page 模型)
// Entry 文件
@Entry
@Component
struct App {
build() {
Navigation() {
NavDestination({ title: 'Todos' }) {
HomePage()
}
NavDestination({ title: 'Detail' }) {
DetailPage()
}
}
}
}
主页:列表 + 新增项动画
@Struct
class Todo {
constructor(public id: number, public text: string, public done: boolean = false) {}
}
@Component
struct HomePage {
@State input: string = ''
@State todos: Todo[] = [ new Todo(1, '读完 ArkUI 状态管理'), new Todo(2, '给按钮加个弹簧动画') ]
private nextId: number = 3
build() {
Column({ space: 12 }) {
// 输入区
Row({ space: 8 }) {
TextInput({ placeholder: '来点啥待办?' })
.width('70%')
.onChange(v => this.input = v)
Button('Add')
.type(ButtonType.Capsule)
.onClick(() => this.addTodo())
}
// 列表
List({ space: 6 }) {
ForEach(this.todos, (item: Todo) => {
ListItem() {
Row({ space: 10 }) {
Checkbox({ name: `todo-${item.id}`, group: 'todos', checked: item.done })
.onChange(v => item.done = v)
Text(item.text)
.decoration({ type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
.fontColor(item.done ? '#999999' : '#222222')
Blank()
Button('Detail')
.onClick(() => {
// 跳转携带数据(可用 AppStorage/参数等方式)
AppStorage.Set('currentTodo', JSON.stringify(item))
router.pushUrl({ url: 'Detail' })
})
}
.padding(10)
}
// 每个 ListItem 出场做个“轻弹”过渡
.transition(TransitionEffect.OPACITY
.combine(TransitionEffect.SCALE)
.animation({ curve: Curve.Spring, duration: 240 }))
})
}
.edgeEffect(EdgeEffect.Spring)
}
.padding(16)
}
addTodo() {
if (!this.input.trim()) return
// 显式动画包裹状态更新,让新增项“蹦”一下
animateTo({ duration: 220, curve: Curve.Spring }, () => {
this.todos = [ new Todo(this.nextId++, this.input), ...this.todos ]
this.input = ''
})
}
}
详情页:@StorageLink 读取全局存的当前项
import router from '@ohos.router'
@Component
struct DetailPage {
@StorageLink('currentTodo') current: string = '' // 简化处理:字符串存储
@State shake: number = 0
get todo(): Todo | null {
try { return JSON.parse(this.current) as Todo } catch { return null }
}
build() {
Column({ space: 14 }) {
if (!this.todo) {
Text('没有找到待办项……').fontColor(Color.Red)
Button('返回').onClick(()=> router.back())
return
}
Text(`📝 ${this.todo!.text}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.translate({ x: this.shake })
.animation({ duration: 120 }) // 隐式抖动
Row({ space: 8 }) {
Button('轻轻提醒一下')
.onClick(()=> this.joyShake())
Button('返回')
.onClick(()=> router.back())
}
}
.padding(20)
}
joyShake() {
// 一个小抖动的显式动画
animateTo({ duration: 80 }, () => this.shake = -6)
setTimeout(()=> animateTo({ duration: 80 }, () => this.shake = 6), 90)
setTimeout(()=> animateTo({ duration: 60 }, () => this.shake = 0), 180)
}
}
这段示例囊括了:Page 路由、局部 @State、AppStorage 跨页共享、隐式/显式动画。你可以把
AppStorage换成@Provide/@Consume或路由参数传递,看团队习惯。
7) 工程结构与常见坑:我踩过的雷你就别踩了
推荐工程分层(思路,不是唯一答案)
/features # 业务特性:todo、user、settings
/todo
components/ # 纯 UI 组件(无副作用)
pages/ # 页面组件(@Entry 或路由承载)
services/ # 数据访问(fetch、dataShare)
store/ # 状态(@Provide/@Consume 封装)
/shared # 公共:hooks、types、theme、ui-kits
/app # 路由、入口、全局 Provider、权限守卫
- UI 与状态解耦:可复用、可单测。
- Provider 下沉:主题、用户、权限走
@Provide,避免到处“传 props”。 - 服务边界清晰:网络请求与数据缓存别混在组件里。
常见坑 & 解法
-
“为啥我 Cookie/参数丢了?”
- 跨页面数据建议用
AppStorage或参数化路由(在 Stage 下可走 Ability 参数)。
- 跨页面数据建议用
-
“列表动画一卡一顿?”
- 列表项尽量不可变更新(创建新数组触发 diff 更明确),避免在大循环里频繁同步 IO。
-
“多窗口怎么办?”(Stage 模型)
- 使用
WindowStage管理窗口生命周期,注意不同窗口共享状态要通过可观察对象或全局存储,并做好并发保护。
- 使用
-
“状态乱飞找不到源头?”
- 规则:自上而下单向数据流。跨层才用
@Provide/@Consume;双向只限局部用@Link。
- 规则:自上而下单向数据流。跨层才用
-
“动画掉帧?”
- 把重计算放到
requestAnimationFrame同步点之外(或拆分),尽量用框架提供的隐式动画,显式动画集中控制、别嵌套太深。
- 把重计算放到
8) 结语 & 你可能的下一步
到这儿,Stage 与 Page 的边界是不是清楚多了?UI 组件的套路是不是也顺手些了?状态与动画这一挂,掌握了**“最少必要原则”**之后,剩下就是不断把需求拆小、抽象沉淀、踩坑复盘。框架是工具,情绪是体验的调味料——让你的界面“动”起来、让交互“说话”,用户才会“心动”。
**你想我继续怎么打磨?**👇
- 要不要我把上面 Demo 改成 Stage 模型版本,演示窗口与 Ability 生命周期?
- 还是把示例升级为一个多 Tab 的小应用(首页/搜索/设置),顺带加上主题切换与持久化?
- 又或者,来个组件库式的 UI 指南(Button、Card、List、Dialog、Toast 的设计范式与统一动效节奏)?
只要你告诉我目标设备(手机/平板/穿戴)和最想实现的业务场景,我就把骨架按你的口味调好,顺带把动画统一成一套“呼吸感”的节奏。咱们把“能跑的 Demo”,打磨成“敢上线的小成品”。😉✨
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」专栏(全网一个名),bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
✨️ Who am I?
我是bug菌(全网一个名),CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主/价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-
- 点赞
- 收藏
- 关注作者
评论(0)