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

举报
bug菌 发表于 2025/12/25 14:56:29 2025/12/25
【摘要】 🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8 😵 摘要我得先坦白一句:我刚写 ArkUI 那会儿,状态管理完全靠“...

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

😵 摘要

我得先坦白一句:我刚写 ArkUI 那会儿,状态管理完全靠“直觉”——页面里 @State 一通狂堆,子组件要数据就 @Prop,要改就“想办法”传回去……结果项目一大,状态像头发一样到处飘🤣:改一个值,牵一串 UI;查一个 bug,追三层组件;最离谱的是——你还不知道它为什么刷新了。
  所以这节咱们就狠狠干一件事:把鸿蒙 ArkUI 的状态机制**按“层级 + 数据流 + 生命周期”**彻底捋顺。你看完应该能做到:选对装饰器、放对存储、少写脏代码、少踩坑😤✅

🧭目录(状态不丢,心态不崩😄)

🧠 状态管理到底在管啥:先把“层级”想明白

我总结一个特别“土”但特别好用的心法:

状态放哪儿,取决于“谁需要它” + “它要活多久”。

你可以按层级把状态分成四类(很像收纳分类,越收越爽🧺):

  1. 🧩 组件私有状态:只有这个组件用,生命周期跟组件走

    • 用:@State
  2. 👨‍👩‍👧‍👦 父子共享状态:父给子看、或子要改父

    • 用:@Prop / @Link
  3. 🧗 跨多层共享状态:中间好几层不关心,但底下要用

    • 用:@Provide / @Consume
  4. 🌍 页面级/应用级状态:跨页面、跨组件都要用

    • 用: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,迟早把自己卡到怀疑人生🥲

🕳️ 高频翻车现场:我替你踩过的坑(含解法)😭

  1. 😤 “对象字段变了 UI 不刷新”

    • 解法:用 @Observed,或者用“新对象替换”让框架感知变化。
  2. 🕸️ @Provide/@Consume 用太多,依赖关系变隐形

    • 解法:只 Provide “全局语义明确”的东西(主题、语言、用户态),业务细节别乱 Provide。
  3. 🌍 AppStorage 全局到处写,谁改了都不知道

    • 解法:全局状态收口管理(比如统一在一个 Store/Service 改),别在各页面随手改。
  4. 🎯 @Link 滥用导致数据流混乱

    • 解法:默认用 @Prop 单向,只有“强交互组件”才用 @Link
  5. 💾 @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-

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。