本文是《鸿蒙开发小白入门学习计划.md》中"状态管理与交互"周(W4)的单独扩写版,沿用"第一行代码"式手把手写法。
主计划表保持精简,本周的详细操作步骤、代码示例与参考链接集中放在本文,便于单独查阅与 CSDN 发布。
前置:已完成 W1(环境跑通)+ W2(ArkTS 语法)+ W3(ArkUI 组件与布局)。本周在 W3 项目里加"交互与状态"。


一、状态管理与交互(手把手操作步骤)

本节采用"第一行代码"式的手把手写法:每条都给可运行代码 + 验证方法。跟着敲,约 4–5 小时能吃透状态装饰器与基本交互。
W4 是 ArkUI 的"分水岭"——学会 @State 之后,你的页面才真正"活"起来(数据变了 UI 自动变)。

学习目标

  • 理解"状态驱动 UI":数据变 → 框架自动重绘,不用手动 setText
  • 掌握 5 组状态装饰器(@State / @Prop / @Link / @Provide-@Consume / @Observed-@ObjectLink)的选用
  • 会用 onClick / onChange / onTouch 处理交互
  • 会用 if/else 控制显隐、ForEach 渲染列表、@Builder 抽复用 UI

本周工具说明(无需新装)

  • 继续在 W1 项目的 entry/src/main/ets/pages/Index.ets 改 build()。
  • 仍推荐 Previewer(预览器) 实时看效果;改状态逻辑后预览器会自动重绘。
  • 关键认知:ArkUI 是声明式 + 状态驱动。你只声明"UI 长什么样(依赖哪些状态)",数据一变框架重算并刷新——这和 Android 老的 findViewById().setText() 思路完全不同,先转过弯来。

步骤一、认识 @State:让数据驱动 UI
@State 是组件内私有、可变的 UI 状态。它一变,用到它的 UI 自动重绘。

// Index.ets
@Entry
@Component
struct Index {
  @State count: number = 0        // ① 用 @State 声明状态

  build() {
    Column() {
      Text(`当前计数:${this.count}`)   // ② 绑定状态
        .fontSize(20)
      Button('加一')
        .onClick(() => {               // ③ 点击事件改状态
          this.count++                 // ④ 直接改,UI 自动刷新
        })
        .margin({ top: 12 })
    }
    .width('100%').height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

验证:预览器点"加一",数字实时 +1。若数字不变,检查 count 是否加了 @State(漏写就不会重绘,这是最高频坑)。

步骤二、事件与交互三件套

事件触发时机典型用法
onClick点击按钮、卡片点击
onChange输入变化TextInput 实时取值
onTouch触摸(按下/移动/抬起)自定义手势、拖拽
TextInput({ placeholder: '请输入姓名' })
  .onChange((value: string) => {     // 每次输入变化都回调
    console.log('输入了:' + value)
  })

Button('长按区')
  .onTouch((e) => {
    if (e.type === TouchType.Down) { console.log('按下了') }
  })

步骤三、@Prop:父传子(单向)
父组件把值传给子组件,子组件用 @Prop 接收;子改 @Prop 不影响父。

// 子组件
@Component
struct Child {
  @Prop title: string = ''          // 父传来的,子可改但只改自己
  build() {
    Text(this.title).fontSize(18)
  }
}
// 父组件
@Component
struct Parent {
  @State msg: string = '你好'
  build() {
    Column() {
      Child({ title: this.msg })    // 传值
      Button('父改值').onClick(() => { this.msg = '已改' })
    }
  }
}

坑:@Prop 是单向——父改会传下去,子改不回传。需要双向用 @Link。

步骤四、@Link:父子双向共享
父子指向同一份状态,任一方改,双方都变。

@Component
struct Child {
  @Link count: number             // 注意:无初始值,靠父传入引用
  build() {
    Button(`子改:${this.count}`)
      .onClick(() => { this.count++ })
  }
}
@Component
struct Parent {
  @State num: number = 0
  build() {
    Column() {
      Text(`父显示:${this.num}`)
      Child({ count: this.num })   // 用 $ 传引用(语法:$变量名)
    }
  }
}

验证:点子组件按钮,父的"父显示"同步 +1——证明双向。传参必须写 Child({ count: this.$num })(缺 $ 会报错)。

步骤五、@Provide / @Consume:跨层级免传参
祖孙多层嵌套时,不想逐层 @Prop 传,用 @Provide(祖先发)配 @Consume(后代收),按变量名自动匹配。

@Component
struct GrandChild {
  @Consume theme: string          // 自动找祖先的 @Provide('theme')
  build() { Text('主题:' + this.theme) }
}
@Entry
@Component
struct Index {
  @Provide theme: string = '浅色'  // 祖先提供
  build() {
    Column() {
      GrandChild()
      Button('换主题').onClick(() => { this.theme = '深色' })
    }
  }
}

适用:全局设置(主题、登录用户、语言)这类"深层都要用"的数据。

步骤六、@Observed / @ObjectLink:监听嵌套对象
普通 @State 只能监听"整个对象替换",监听不到对象内部属性变化。嵌套对象用 @Observed(类)+ @ObjectLink(子组件)解决。

@Observed
class User {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
@Component
struct Card {
  @ObjectLink user: User          // 接收嵌套对象
  build() {
    Column() {
      Text(this.user.name)
      Button('涨一岁').onClick(() => { this.user.age++ })  // 改内部属性,UI 会刷新
    }
  }
}
@Entry
@Component
struct Index {
  @State u: User = new User('张三', 18)
  build() {
    Column() { Card({ user: this.u }) }
  }
}

坑:没加 @Observed 时改 user.age 不会重绘——这是对象状态最常见的"为什么没刷新"。

步骤七、条件与循环渲染

  • if/else 控制显隐(注意:条件渲染的组件不能带状态变量做兄弟,结构要稳定):
@State show: boolean = true
build() {
  Column() {
    if (this.show) {
      Text('显示了')
    } else {
      Text('隐藏了')
    }
    Button('切换').onClick(() => { this.show = !this.show })
  }
}
  • ForEach 渲染列表(第二个参数是 key 生成函数,key 要唯一稳定):
@State list: string[] = ['苹果', '香蕉']
build() {
  Column() {
    ForEach(this.list, (item: string, index: number) => {
      Text(`${index + 1}. ${item}`).fontSize(16)
    }, (item: string) => item)   // key 生成器
  }
}

步骤八、@Builder:抽取复用 UI 片段
多处重复的 UI 用 @Builder 抽成方法,减少复制粘贴。

@Builder
itemCell(text: string) {            // 定义可复用片段
  Row() {
    Text(text).fontSize(16)
    Blank()
    Image($r('app.media.arrow')).width(16).height(16)
  }
  .width('100%').padding(12)
}
build() {
  Column() {
    this.itemCell('我的订单')      // 像方法一样调用
    this.itemCell('我的收藏')
  }
}

步骤九、综合练习(验收用)
做两个小东西,覆盖本周核心:

  1. 计数器:@State + onClick 自增/自减(步骤一升级版,加"减一"按钮)。
  2. 待办列表雏形:
@Entry
@Component
struct Index {
  @State tasks: string[] = []
  @State input: string = ''

  build() {
    Column() {
      // 输入 + 添加
      Row() {
        TextInput({ placeholder: '输入待办', text: this.input })
          .onChange((v) => { this.input = v })
          .layoutWeight(1)
        Button('添加').onClick(() => {
          if (this.input.trim() !== '') {
            this.tasks.push(this.input)   // 改数组,ForEach 自动刷新
            this.input = ''
          }
        })
      }.width('100%').margin({ bottom: 12 })

      // 列表渲染
      ForEach(this.tasks, (t: string) => {
        Text('• ' + t).fontSize(16).padding(8)
      }, (t: string) => t)
    }
    .width('100%').height('100%').padding(16)
  }
}

验收:① 点按钮数字变化并即时重绘;② 输入框打字、点"添加"后内容进列表并显示;③ 空输入不添加(trim 判断)。

必须记住的点

  • ArkUI = 声明式 + 状态驱动:改 @State 数据 → 框架自动重绘,别再想 findViewById().setText()。
  • 装饰器选型口诀:自己用→@State;父传子单向→@Prop;父子双向→@Link;跨层全局→@Provide/@Consume;嵌套对象内部变化→@Observed/@ObjectLink。
  • ForEach 必须给唯一稳定的 key,否则列表增删会错乱。
  • @Link 传参要写 $变量名,@Prop 不需要。

常见坑(提前避)

  1. 改了数据 UI 不变 → 90% 是没加 @State / 忘了 @Observed(嵌套对象)。
  2. @Link 传参漏写 $(应为 Child({ count: this.$num })),编译报错"类型不匹配"。
  3. ForEach 的 key 用了数组下标 index——数据增删时 UI 错位,要用内容当 key。
  4. if/else 条件块里放带 @State 的组件做兄弟,结构不稳定导致渲染异常;保持条件分支内结构清晰。
  5. TextInput 不绑定 @State 就取不到值——用 text + onChange 双向绑。
  6. @Builder 里若用到外部状态,注意它默认不感知组件状态,复杂场景用 @BuilderParam 或抽成子组件。

实操任务:完成上面 9 步 → 做出"计数器"(加减)和"待办列表雏形"(输入+添加+列表渲染)。
验收:计数器点按即时变化;待办输入能进列表显示、空输入不添加;能把"为什么 UI 会自动刷新"讲清楚(状态驱动)。


二、参考学习链接

1. 官方下载

2. 官方文档

3. 社区与踩坑

4. Markdown 写作参考(CSDN 发布用)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐