你还在到处乱塞 @State?状态一多就“爆炸”,你真不想优雅点吗?

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
😵 摘要
我得先坦白一句:我刚写 ArkUI 那会儿,状态管理完全靠“直觉”——页面里 @State 一通狂堆,子组件要数据就 @Prop,要改就“想办法”传回去……结果项目一大,状态像头发一样到处飘🤣:改一个值,牵一串 UI;查一个 bug,追三层组件;最离谱的是——你还不知道它为什么刷新了。
所以这节咱们就狠狠干一件事:把鸿蒙 ArkUI 的状态机制**按“层级 + 数据流 + 生命周期”**彻底捋顺。你看完应该能做到:选对装饰器、放对存储、少写脏代码、少踩坑😤✅
🧭目录(状态不丢,心态不崩😄)
- 🧠 状态管理到底在管啥:先把“层级”想明白
- 🎯 @State / @Prop / @Link:最常用的三兄弟怎么选?
- 🤝 @Provide / @Consume:跨层传值,别再手拉手传参了🙏
- 🧬 @Observed / @Watch:对象可观测与变化监听
- 🧰 AppStorage / LocalStorage:全局与页面级存储怎么放?
- 💾 持久化存储:存哪里更靠谱?别把 Preferences 当数据库😅
- 🕳️ 高频翻车现场:我替你踩过的坑(含解法)😭
🧠 状态管理到底在管啥:先把“层级”想明白
我总结一个特别“土”但特别好用的心法:
状态放哪儿,取决于“谁需要它” + “它要活多久”。
你可以按层级把状态分成四类(很像收纳分类,越收越爽🧺):
-
🧩 组件私有状态:只有这个组件用,生命周期跟组件走
- 用:
@State
- 用:
-
👨👩👧👦 父子共享状态:父给子看、或子要改父
- 用:
@Prop / @Link
- 用:
-
🧗 跨多层共享状态:中间好几层不关心,但底下要用
- 用:
@Provide / @Consume
- 用:
-
🌍 页面级/应用级状态:跨页面、跨组件都要用
- 用:
LocalStorage / AppStorage
- 用:
然后再加一类:
5. 💾 跨启动仍存在的状态(重启也不丢)
- 用:持久化存储(Preferences 等)
你看,状态管理其实不是“语法”,是信息架构。架构对了,代码自然顺;架构错了,你写再多“技巧”也救不了🤣
🎯 @State / @Prop / @Link:最常用的三兄弟怎么选?
🟢 1)@State:组件自己管自己(最爽,也最容易滥用😅)
@Entry
@Component
struct StateDemo {
@State count: number = 0
build() {
Column() {
Text(`😄 count = ${this.count}`)
Button('➕ +1').onClick(() => this.count++)
}.padding(20)
}
}
啥时候用?
- 只有当前组件需要
- 改了就刷新当前 UI
- 不用让别的组件知道
别滥用的信号:
- 子组件也要用这个状态
- 换个页面还得用
那它就不配只放@State里了(它“格局”更大😏)
🟡 2)@Prop:父传子(子组件别乱改,乖😇)
@Component
struct ChildProp {
@Prop title: string
build() {
Text(`📦 子组件看到:${this.title}`)
}
}
@Entry
@Component
struct ParentProp {
@State title: string = '来自父组件的标题✨'
build() {
Column() {
ChildProp({ title: this.title })
Button('✏️ 父改标题').onClick(() => this.title = '父组件更新啦!')
}.padding(20)
}
}
关键词:单向数据流 ✅
父变 → 子跟着变
但子一般不应该直接改父的数据(除非你想让数据流像麻花一样拧着😵)
🟠 3)@Link:双向绑定(甜蜜的负担🤣)
@Component
struct ChildLink {
@Link count: number
build() {
Row() {
Button('➖').onClick(() => this.count--)
Text(` ${this.count} `).fontSize(18).margin({ left: 8, right: 8 })
Button('➕').onClick(() => this.count++)
}
}
}
@Entry
@Component
struct ParentLink {
@State count: number = 1
build() {
Column() {
Text(`🏠 父组件 count:${this.count}`)
ChildLink({ count: $count })
}.padding(20)
}
}
啥时候用?
- 子组件需要直接修改父的状态
- 交互非常紧密(比如 stepper、开关、数量选择)
我个人的“唠叨建议”:
- 小范围用
@Link很香😋 - 大范围全靠
@Link会让你后期调试想“出家”😭
🤝 @Provide / @Consume:跨层传值,别再手拉手传参了🙏
有一种痛你肯定懂:
父 → 子 → 孙 → 曾孙……
中间组件根本不关心,但你还得一层层 @Prop 传下去。传着传着你就开始骂人了🤣
这时候 @Provide / @Consume 就像“电梯直达”,不用每层楼都停。
✅ 示例:主题色 / 用户信息跨层共享
@Entry
@Component
struct ProvideDemo {
@Provide('themeColor') themeColor: string = '#2E7DFF'
@State userName: string = 'Precious' // 举例哈😄
build() {
Column() {
Text('🏠 父组件:提供 themeColor').fontSize(18)
Button('🎨 换主题').onClick(() => {
this.themeColor = this.themeColor === '#2E7DFF' ? '#FF6B6B' : '#2E7DFF'
})
MidLayer()
}.padding(20)
}
}
@Component
struct MidLayer {
build() {
Column() {
Text('🧱 中间层:我不关心 themeColor,但我负责把 UI 组装起来🙃')
DeepChild()
}.margin({ top: 12 })
}
}
@Component
struct DeepChild {
@Consume('themeColor') themeColor: string
build() {
Text(`🎯 深层子组件拿到 themeColor:${this.themeColor}`)
.fontColor(this.themeColor)
.margin({ top: 12 })
}
}
适用场景:
- 主题、语言、登录态、用户信息等“跨层但稳定”的共享数据
- 不想中间层被迫当“搬运工”😅
注意⚠️:
@Provide的 key(这里是'themeColor')要规范统一- 别把所有东西都 Provide 成“全家桶”,不然依赖关系会变得隐形🕳️
🧬 @Observed / @Watch:对象可观测与变化监听
很多人刚上手会在这翻车:
**“我改了对象的字段,UI 怎么不刷新?”**😤
因为在声明式体系里,“对象内部变化是否可被感知”非常关键。
✅ 1)@Observed:让类实例变得“可观测”👀
@Observed
class Profile {
name: string = '小明'
age: number = 18
}
@Entry
@Component
struct ObservedDemo {
@State profile: Profile = new Profile()
build() {
Column() {
Text(`👤 ${this.profile.name}, age=${this.profile.age}`)
Button('🎂 age + 1').onClick(() => {
this.profile.age += 1
})
}.padding(20)
}
}
直观理解:
@Observed像给对象装了“传感器”,字段变了能通知 UI。
✅ 2)@Watch:监听变化后做副作用(比如存储、请求、埋点…)🧨
@Entry
@Component
struct WatchDemo {
@State keyword: string = ''
@Watch('onKeywordChange')
keywordWatcher: string = ''
onKeywordChange() {
// 这里放副作用:比如搜索请求、写缓存、打日志
console.info(`🔎 keyword changed: ${this.keyword}`)
}
build() {
Column() {
TextInput({ placeholder: '输入关键词...' })
.onChange(v => this.keyword = v)
}.padding(20)
}
}
我提醒一句很关键的:
@Watch适合“状态变化后的副作用”- 但副作用别太重,别一变就狂发请求,不然你会把自己服务器打哭😭
🧰 AppStorage / LocalStorage:全局与页面级存储怎么放?
这俩你可以理解成:
AppStorage:应用级全局仓库(跨页面、跨组件都能读写)🌍LocalStorage:页面级仓库(在某个页面树内共享)🏠
🌍 AppStorage:全局主题 / 登录态这种就很适合
// 设置全局值
AppStorage.setOrCreate('globalTheme', 'light')
@Entry
@Component
struct AppStorageDemo {
@StorageLink('globalTheme') theme: string = 'light'
build() {
Column() {
Text(`🌍 当前全局主题:${this.theme}`)
Button('🌓 切换主题').onClick(() => {
this.theme = this.theme === 'light' ? 'dark' : 'light'
})
}.padding(20)
}
}
@StorageLink:把 AppStorage 的某个 key 跟当前组件变量绑定起来(很方便,也很“危险”——别乱绑🤣)
🏠 LocalStorage:页面内共享(别让全局到处都是)
@Entry
@Component
struct LocalStoragePage {
private pageStorage: LocalStorage = new LocalStorage()
aboutToAppear() {
this.pageStorage.setOrCreate('pageCount', 0)
}
build() {
// 关键:把 LocalStorage 注入到子树
Column() {
PageChild()
}
.padding(20)
.storage(this.pageStorage)
}
}
@Component
struct PageChild {
@LocalStorageLink('pageCount') count: number = 0
build() {
Column() {
Text(`🏠 页面级 count:${this.count}`)
Button('➕ +1').onClick(() => this.count++)
}
}
}
我的偏好(很个人哈😄):
- 能用 LocalStorage 就别上 AppStorage
- 全局状态越少越好,不然“谁改了它”会变成悬案🕵️
💾 持久化存储:存哪里更靠谱?别把 Preferences 当数据库😅
持久化的本质:重启后还要在。
常见选择(从轻到重):
- 🧾 Preferences(轻量键值):设置项、小体量列表、开关等
- 🗃️ 数据库/更结构化存储:大数据、复杂查询、关系型需求(看你项目选)
- ☁️ 云端同步/分布式场景:多设备一致性需求(更进阶)
✅ 示例:用 Preferences 存一个 Todo 列表(轻量刚刚好)
import preferences from '@ohos.data.preferences'
class TodoPersistence {
private name: string
private key: string = 'todo_list'
constructor(name: string) {
this.name = name
}
async save(list: object[]) {
const pref = await preferences.getPreferences(getContext(), this.name)
await pref.put(this.key, JSON.stringify(list))
await pref.flush()
}
async load(): Promise<object[]> {
const pref = await preferences.getPreferences(getContext(), this.name)
const raw = await pref.get(this.key, '[]') as string
try {
return JSON.parse(raw)
} catch (e) {
return []
}
}
}
我必须再碎嘴一句:
- Preferences 适合“小而美”
- 你拿它塞几 MB 的大 JSON,迟早把自己卡到怀疑人生🥲
🕳️ 高频翻车现场:我替你踩过的坑(含解法)😭
-
😤 “对象字段变了 UI 不刷新”
- 解法:用
@Observed,或者用“新对象替换”让框架感知变化。
- 解法:用
-
🕸️ @Provide/@Consume 用太多,依赖关系变隐形
- 解法:只 Provide “全局语义明确”的东西(主题、语言、用户态),业务细节别乱 Provide。
-
🌍 AppStorage 全局到处写,谁改了都不知道
- 解法:全局状态收口管理(比如统一在一个 Store/Service 改),别在各页面随手改。
-
🎯 @Link 滥用导致数据流混乱
- 解法:默认用
@Prop单向,只有“强交互组件”才用@Link。
- 解法:默认用
-
💾 @Watch 里做重活,输入框打字都卡
- 解法:节流/防抖;副作用轻量化;必要时把耗时任务移出 UI 线程(别把 UI 当苦力😭)
✅最后的“选型口诀”(我真靠它活命😅)
- 只在当前组件用 →
@State - 父传子展示 →
@Prop - 子要改父(小范围交互)→
@Link - 跨层共享(主题/用户态)→
@Provide/@Consume - 对象内部字段可观测 →
@Observed - 变化触发副作用 →
@Watch - 跨页面共享 →
AppStorage(少用但要用对) - 页面内共享 →
LocalStorage(更推荐) - 重启不丢 → 持久化(Preferences/DB/云)
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学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)