👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】
   我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 HarmonyOS 的过程都记录在这里。
  
  🛠️ 主要方向:ArkTS 语言基础、HarmonyOS 原生应用(Stage 模型、UIAbility/ServiceAbility)、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战,以及 Android → 鸿蒙的迁移踩坑与复盘。
  🧭 内容节奏:从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘,让每篇都有可落地的代码与方法论。
  💡 我相信:写作是把知识内化的过程,分享是让生态更繁荣的方式。
  
   如果你也想拥抱鸿蒙、热爱成长,欢迎关注我,一起交流进步!🚀

前言

我承认,我也干过这种事:页面一复杂就开始“见缝插针”加 @State,结果最后的效果是——数据在组件树里像弹珠一样乱滚:改了子组件,父组件没同步;页面跳转回来,状态丢了;更离谱的是偶发卡顿,我还以为是手机在针对我……🤦‍♂️
  后来我才彻底想通:ArkUI 的状态管理不是“装饰器背诵题”,而是“数据流设计题”。你设计对了,UI 像开了自动挡;你设计错了,整套工程就是手动挡还没离合😵‍💫。

ArkUI 的核心前提是:UI 是状态的运行结果,状态变量必须用装饰器声明,状态变化会触发 UI 重新渲染。(华为开发者)
并且,状态管理(V1)支持组件级与应用级两大类装饰器,覆盖父子传递、跨层级传递、应用全局同步等场景。(华为开发者)

一、@State / @Prop / @Link:三兄弟别乱用,不然你会被“同步关系”背刺😤

先来一段官方“定性”(我只提最关键的那几句):

  • @State:组件自身状态,可作为子组件单/双向同步的数据源,变化会触发相关组件刷新。(华为开发者)
  • @Prop:与父组件建立单向同步,子组件里能改,但不会同步回父组件。(华为开发者)
  • @Link:与父组件建立双向同步,子组件修改会同步父组件,父组件更新也会同步子组件。(华为开发者)

我给你翻译成“人话口诀”:

  • @State:我自己的小情绪(组件内部自嗨)
  • @Prop:你给我啥我就用啥,但我不负责告诉你我改了啥(单向)
  • @Link:咱俩绑一根绳,你动我也动(双向)

1) 父子单向:@State@Prop(推荐做配置、展示)

@Component
struct PriceTag {
  @Prop price: number = 0 // 父传子:单向

  build() {
    Text(`${this.price.toFixed(2)}`)
      .fontSize(20)
  }
}

@Entry
@Component
struct ProductPage {
  @State price: number = 199.9

  build() {
    Column({ space: 12 }) {
      PriceTag({ price: this.price })
      Button('涨价(只改父)')
        .onClick(() => this.price += 10)
    }.padding(16)
  }
}

2) 父子双向:@State@Link(推荐做输入、开关、选择器)

@Component
struct KeywordInput {
  @Link keyword: string

  build() {
    TextInput({ text: this.keyword, placeholder: '请输入关键字' })
      .onChange(v => this.keyword = v) // 改子=改父
  }
}

@Entry
@Component
struct SearchPage {
  @State keyword: string = ''

  build() {
    Column({ space: 12 }) {
      KeywordInput({ keyword: this.keyword })
      Text(`你输入的是:${this.keyword}`)
    }.padding(16)
  }
}

小提醒:@Prop 能改但不回写,很多“怎么父组件没变”的疑难杂症,其实就是你该用 @Link 却用了 @Prop(别问我怎么知道的🙃)。

二、状态提升与单向数据流:别让状态到处散步,它会迷路的😅

所谓“状态提升”(State Hoisting),本质就一句话:
谁负责业务决策,状态就放谁那;子组件尽量做“展示 + 事件上报”。

你想要的通常是“单向数据流”:

  • 父组件:持有状态 + 负责更新
  • 子组件:拿到状态(@Prop)+ 抛出事件(callback)
  • 更新路径只有一条:事件 → 父更新状态 → UI 重渲染

1) 推荐:子组件不直接改状态,而是“发事件”

@Component
struct CounterView {
  @Prop count: number = 0
  onInc?: () => void

  build() {
    Row({ space: 12 }) {
      Text(`${this.count}`).fontSize(22)
      Button('+1').onClick(() => this.onInc?.())
    }
  }
}

@Entry
@Component
struct CounterPage {
  @State count: number = 0

  build() {
    Column({ space: 16 }) {
      CounterView({
        count: this.count,
        onInc: () => this.count += 1
      })
    }.padding(16)
  }
}

这种写法看起来“绕了一下”,但好处是巨大的:

  • 状态更新入口唯一,可控、可测、可追踪
  • 复杂页面也不容易“互相改来改去改炸了”

三、页面间状态同步:别靠“全局变量+祈祷”,用对 AppStorage / LocalStorage 才像正经工程🙏

你一旦涉及页面跳转、多个页面共享状态,就会遇到灵魂拷问:
“这个状态到底属于页面,还是属于应用?”

官方把装饰器按影响范围分为:

  • 组件级(同页面/同组件树)
  • 应用级(跨页面甚至跨 UIAbility 的全局状态)(华为开发者)

1) 应用级全局:AppStorage(跨页面共享很常用)

AppStorage 是应用级的状态“中枢”,可以用 @StorageProp/@StorageLink 让组件与全局状态单向/双向联动。(华为开发者)

例子:登录态 / 用户名,全局任何页面都能读写:

// UserStore.ts(示意)
import { AppStorage } from '@kit.ArkUI' // 不同模板导入路径可能略有差异

export const KEY_USER_NAME = 'userName'

// 初始化一次(比如在 entry 的 aboutToAppear 里做)
export function initUserStore() {
  if (AppStorage.get(KEY_USER_NAME) === undefined) {
    AppStorage.setOrCreate(KEY_USER_NAME, '')
  }
}
// ProfilePage.ets
@Entry
@Component
struct ProfilePage {
  @StorageLink(KEY_USER_NAME) userName: string = ''  // 双向:改我=改全局 :contentReference[oaicite:7]{index=7}

  build() {
    Column({ space: 12 }) {
      Text(`当前用户:${this.userName || '未登录'}`)
      TextInput({ text: this.userName, placeholder: '输入昵称全局生效' })
        .onChange(v => this.userName = v)
    }.padding(16)
  }
}

2) 页面级共享:LocalStorage(更像“这个页面体系里的小数据库”)

官方同样指出:LocalStorage 常用于页面级状态共享,可用 @LocalStorageProp/@LocalStorageLink 和 UI 联动。(华为开发者)
(你把它理解成:“只想在某个页面栈/页面域里共享,不想污染全局”。)

3) 状态管理 V2:AppStorageV2(新能力别硬上,先看适用范围)

HarmonyOS 文档提供了 AppStorageV2 相关机制作为状态管理(V2)的一部分。(华为开发者)
我的建议很“俗”:老项目先把 V1 用明白,能解决 80% 的状态混乱;需要更清晰的全局状态模型、或团队在做统一架构升级,再引入 V2 会更稳。

四、状态管理最佳实践:写得爽不算本事,跑得稳才是😎

官方最佳实践里明确在讲一件事:错误使用状态变量会引入不必要的查询和渲染,导致性能劣化;推荐用临时变量减少不必要行为。(华为开发者)
这真的不是纸上谈兵——你在列表、日志拼接、字符串处理这些地方乱改状态,掉帧是分分钟的事。

我给你一套“能救命”的清单(每条都很实用):

1) 计算别直接怼状态:先用临时变量算完再回写

@State msg: string = ''

append(newMsg: string) {
  // ✅ 先用临时变量拼接,最后一次性写回
  let tmp = this.msg
  tmp += newMsg + ';'
  this.msg = tmp
}

这类写法正是官方在优秀实践里强调的方向:减少不必要的状态查询/渲染行为。(华为开发者)

2) 状态粒度要小:能局部就别全局

  • 一个页面里别用一个巨大的 @State bigObject 装下所有东西
  • 拆成多个小状态,或用更合适的结构(必要时配合 @Observed/@ObjectLink 处理嵌套对象)
    官方也说明:嵌套场景只有 @Observed/@ObjectLink 能观察更深层变化,其它装饰器通常只观察第一层。(华为开发者)

3) 少用“双向”滥绑定:@Link 很香,但别拿来当万能胶

  • 输入框、开关这种“天然双向”的控件用 @Link 合理
  • 复杂业务流建议用“单向 + 事件回传”,不然数据链路太多,出了问题你会追到怀疑人生😵‍💫

4) 明确状态归属:页面态 vs 应用态

  • 页面内临时筛选条件:LocalStorage / 组件级状态
  • 登录态、主题、语言:AppStorage(全局)(华为开发者)
    归属明确了,你的工程结构就不会像“临时工干活”——到处一坨一坨的。

5) 记住一个底线:状态管理只支持 UI 主线程

官方说明:当前状态管理功能仅支持在 UI 主线程使用,不能在子线程/worker/taskpool 中使用。(华为开发者)
所以你别想着在 worker 里改 @State 让 UI 自己动——那不是“高级”,那是“等着出事”😅

收尾:你以为你在写 UI,其实你在写“数据的秩序”🧩

状态管理写得好,UI 就像自动驾驶;写得乱,UI 就像你手忙脚乱同时拧三只方向盘。
  我最后送你一句很欠揍但很真实的反问:
**你现在页面的“状态”,到底是在服务业务,还是在折磨你自己?**😏

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 移动端 老兵
📅 日期:2025-11-05
🧵 本文原创,转载请注明出处。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐