鸿蒙状态管理一文通:@State/@Prop/@Link/@Provide四大装饰器,15分钟彻底搞懂
本文将系统讲解ArkUI四大核心状态管理装饰器:@State、@Prop、@Link、@Provide/@Consume,并通过5个完整的实战案例帮助你彻底掌握它们的使用场景和最佳实践。
📖 鸿蒙NEXT开发实战系列 | 第16篇 | 状态管理篇 🎯 适合人群:有ArkTS基础的开发者 ⏰ 阅读时间:约15分钟 | 💻 开发环境:DevEco Studio 5.0+
上一篇:DevEco Studio必备工具清单 | 下一篇:敬请期待
目录
一、引言:为什么状态管理如此重要?
在鸿蒙NEXT应用开发中,状态管理是构建动态UI的核心基石。想象一下:电商App的商品数量无法实时更新、用户的登录状态无法同步到各个页面、表单输入无法即时反馈——这样的应用体验将是灾难性的。
ArkUI采用声明式UI范式,其核心思想是"状态驱动UI":当状态(State)发生变化时,框架会自动更新对应的UI组件。这种模式相比传统的命令式UI,代码更简洁、逻辑更清晰、维护更方便。
本文将系统讲解ArkUI四大核心状态管理装饰器:@State、@Prop、@Link、@Provide/@Consume,并通过5个完整的实战案例帮助你彻底掌握它们的使用场景和最佳实践。
二、@State装饰器:组件内的状态管家
2.1 基本概念
@State是最基础的状态装饰器,用于管理组件内部的状态数据。被@State装饰的变量具有以下特性:
-
响应式:变量值改变时,自动触发UI刷新
-
私有化:仅在当前组件内可见和可修改
-
初始化:支持在组件创建时进行初始化
2.2 基础用法
@Component
struct CounterComponent {
// 使用@State装饰器声明状态变量
@State count: number = 0
build() {
Column() {
Text(`当前计数:${this.count}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
Row() {
Button('减少')
.onClick(() => {
// 直接修改@State变量,UI会自动更新
this.count--
})
.margin({ right: 10 })
Button('增加')
.onClick(() => {
this.count++
})
.margin({ left: 10 })
}
}
.width('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
}
}
2.3 实战案例一:计数器应用
// 实战案例:功能完整的计数器
@Entry
@Component
struct CounterApp {
@State count: number = 0
@State step: number = 1
@State history: number[] = []
build() {
Column() {
// 标题
Text('计数器应用')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 30 })
// 计数显示
Text(`${this.count}`)
.fontSize(72)
.fontWeight(FontWeight.Bold)
.fontColor('#1890FF')
.margin({ bottom: 20 })
// 步长设置
Row() {
Text('步长:')
.fontSize(16)
Button('-')
.onClick(() => {
if (this.step > 1) {
this.step--
}
})
.width(40)
.height(40)
Text(`${this.step}`)
.fontSize(18)
.margin({ left: 10, right: 10 })
Button('+')
.onClick(() => {
this.step++
})
.width(40)
.height(40)
}
.margin({ bottom: 30 })
// 操作按钮
Row() {
Button('减少')
.onClick(() => {
this.count -= this.step
this.history = [...this.history, this.count] // 记录历史
})
.margin({ right: 15 })
Button('重置')
.onClick(() => {
this.count = 0
this.history = []
})
.margin({ right: 15 })
Button('增加')
.onClick(() => {
this.count += this.step
this.history = [...this.history, this.count]
})
}
.margin({ bottom: 30 })
// 历史记录
if (this.history.length > 0) {
Text('操作历史:')
.fontSize(16)
.margin({ bottom: 10 })
Text(this.history.join(' → '))
.fontSize(14)
.fontColor('#666666')
}
}
.width('100%')
.height('100%')
.padding(20)
.justifyContent(FlexAlign.Center)
}
}
2.4 @State支持的数据类型
@State支持多种数据类型,但不同类型的行为略有差异:
|
数据类型 |
触发更新方式 |
注意事项 |
|---|---|---|
|
number/string/boolean |
直接赋值 |
最简单,推荐使用 |
|
对象(Object) |
整体赋值新对象 |
直接修改属性不会触发更新 |
|
数组(Array) |
整体赋值或splice |
push/pop不会触发更新 |
对象类型正确用法:
interface UserInfo {
name: string
age: number
}
@Component
struct ObjectDemo {
@State user: UserInfo = { name: '张三', age: 25 }
build() {
Column() {
Text(`用户名:${this.user.name}`)
Button('更新用户名')
.onClick(() => {
// 正确方式:整体赋值
this.user = { ...this.user, name: '李四' }
})
}
}
}
三、@Prop装饰器:父子组件的单向数据流
3.1 基本概念
@Prop用于实现父组件到子组件的单向数据传递。特点如下:
-
单向绑定:数据只能从父组件流向子组件
-
本地副本:子组件接收到的是父组件数据的副本,修改不会影响父组件
-
自动同步:父组件数据变化时,子组件自动更新
3.2 实战案例二:商品列表
// 商品数据接口
interface Product {
id: number
name: string
price: number
image: string
}
// 商品卡片组件(子组件)
@Component
struct ProductCard {
@Prop product: Product // 使用@Prop接收父组件数据
@Prop isSelected: boolean // 选中状态
build() {
Column() {
// 商品图片(使用Text模拟)
Text(this.product.image)
.fontSize(48)
.margin({ bottom: 10 })
// 商品名称
Text(this.product.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 5 })
// 商品价格
Text(`¥${this.product.price}`)
.fontSize(18)
.fontColor('#FF4D4F')
.margin({ bottom: 10 })
// 选中状态标识
if (this.isSelected) {
Text('已选中')
.fontSize(12)
.fontColor('#52C41A')
.backgroundColor('#F6FFED')
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(4)
}
}
.width(150)
.padding(15)
.backgroundColor(this.isSelected ? '#E6F7FF' : '#FFFFFF')
.borderRadius(12)
.shadow({
radius: 8,
color: '#0000001A',
offsetX: 0,
offsetY: 2
})
.margin({ right: 10 })
}
}
// 商品列表父组件
@Entry
@Component
struct ProductListDemo {
@State products: Product[] = [
{ id: 1, name: '智能手机', price: 4999, image: '📱' },
{ id: 2, name: '笔记本电脑', price: 8999, image: '💻' },
{ id: 3, name: '无线耳机', price: 999, image: '🎧' },
{ id: 4, name: '智能手表', price: 1999, image: '⌚' }
]
@State selectedId: number = -1
build() {
Column() {
Text('商品列表')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 横向滚动商品列表
Scroll() {
Row() {
ForEach(this.products, (product: Product) => {
ProductCard({
product: product, // 传递商品数据
isSelected: product.id === this.selectedId // 传递选中状态
})
.onClick(() => {
this.selectedId = product.id // 更新选中状态
})
})
}
}
.scrollable(ScrollDirection.Horizontal)
.width('100%')
// 选中商品信息
if (this.selectedId !== -1) {
Text(`已选中:${this.products.find(p => p.id === this.selectedId)?.name}`)
.fontSize(18)
.margin({ top: 20 })
}
}
.width('100%')
.height('100%')
.padding(20)
}
}
3.3 @Prop的关键特性
// @Prop是只读的,子组件不能修改
@Component
struct ReadOnlyDemo {
@Prop message: string
build() {
Column() {
Text(this.message)
Button('尝试修改')
.onClick(() => {
// this.message = '新消息' // 编译错误!@Prop是只读的
console.info('Prop不能在子组件中修改')
})
}
}
}
四、@Link装饰器:父子组件的双向数据绑定
4.1 基本概念
@Link用于实现父子组件之间的双向数据绑定。特点如下:
-
双向绑定:父子组件中任何一方修改数据,另一方都会同步更新
-
同一引用:父子组件共享同一个数据源
-
必须初始化:父组件必须使用
$$操作符传递数据
4.2 实战案例三:设置页面
// 设置项组件(子组件)
@Component
struct SettingItem {
@Link isEnabled: boolean // 使用@Link实现双向绑定
label: string = ''
build() {
Row() {
Text(this.label)
.fontSize(16)
.layoutWeight(1)
// 开关组件
Toggle({ type: ToggleType.Switch, isOn: this.isEnabled })
.onChange((isOn: boolean) => {
this.isEnabled = isOn // 子组件修改,父组件同步更新
})
}
.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ bottom: 10 })
}
}
// 设置页面(父组件)
@Entry
@Component
struct SettingsPage {
@State notifications: boolean = true
@State darkMode: boolean = false
@State autoUpdate: boolean = true
@State locationService: boolean = false
build() {
Column() {
Text('应用设置')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 30 })
// 设置项列表
SettingItem({ isEnabled: $notifications, label: '推送通知' })
SettingItem({ isEnabled: $darkMode, label: '深色模式' })
SettingItem({ isEnabled: $autoUpdate, label: '自动更新' })
SettingItem({ isEnabled: $locationService, label: '定位服务' })
// 显示当前设置状态
Column() {
Text('当前设置状态:')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
Text(`通知: ${this.notifications ? '开启' : '关闭'}`)
Text(`深色模式: ${this.darkMode ? '开启' : '关闭'}`)
Text(`自动更新: ${this.autoUpdate ? '开启' : '关闭'}`)
Text(`定位服务: ${this.locationService ? '开启' : '关闭'}`)
}
.padding(15)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F0F2F5')
}
}
4.3 @Link与@Prop的关键区别
// 对比示例
@Component
struct PropChild {
@Prop value: number
build() {
Column() {
Text(`Prop子组件:${this.value}`)
Button('修改(无效)')
.onClick(() => {
// this.value = 100 // 编译错误
})
}
.padding(10)
.backgroundColor('#FFEBEE')
}
}
@Component
struct LinkChild {
@Link value: number
build() {
Column() {
Text(`Link子组件:${this.value}`)
Button('修改(有效)')
.onClick(() => {
this.value = 100 // 可以修改,父组件同步更新
})
}
.padding(10)
.backgroundColor('#E8F5E9')
}
}
@Entry
@Component
struct ComparisonDemo {
@State propValue: number = 0
@State linkValue: number = 0
build() {
Column() {
PropChild({ value: this.propValue })
LinkChild({ value: $linkValue }) // 注意:使用$操作符
}
.width('100%')
.padding(20)
}
}
五、@Provide/@Consume装饰器:跨层级数据共享
5.1 基本概念
@Provide和@Consume用于实现跨组件层级的数据共享,无需通过中间组件逐层传递。特点如下:
-
跨层级传递:可以在任意深度的子组件中访问数据
-
双向绑定:
@Provide和@Consume之间是双向的 -
一对多:一个
@Provide可以对应多个@Consume
5.2 实战案例四:主题切换系统
// 主题配置接口
interface ThemeConfig {
primaryColor: string
backgroundColor: string
textColor: string
isDark: boolean
}
// 预定义主题
const lightTheme: ThemeConfig = {
primaryColor: '#1890FF',
backgroundColor: '#FFFFFF',
textColor: '#333333',
isDark: false
}
const darkTheme: ThemeConfig = {
primaryColor: '#4DABF7',
backgroundColor: '#1A1A1A',
textColor: '#FFFFFF',
isDark: true
}
// 主题提供者(顶层组件)
@Entry
@Component
struct ThemeProvider {
@Provide('theme') theme: ThemeConfig = lightTheme
build() {
Column() {
// 应用头部
AppHeader()
// 应用内容
AppContent()
// 主题切换按钮
Button(`切换到${this.theme.isDark ? '浅色' : '深色'}模式`)
.onClick(() => {
this.theme = this.theme.isDark ? lightTheme : darkTheme
})
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.backgroundColor(this.theme.backgroundColor)
}
}
// 应用头部组件
@Component
struct AppHeader {
@Consume('theme') theme: ThemeConfig // 消费主题配置
build() {
Row() {
Text('鸿蒙应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(this.theme.textColor)
}
.width('100%')
.padding(15)
.backgroundColor(this.theme.primaryColor)
}
}
// 应用内容组件
@Component
struct AppContent {
@Consume('theme') theme: ThemeConfig
build() {
Column() {
Text('欢迎使用鸿蒙应用')
.fontSize(24)
.fontColor(this.theme.textColor)
.margin({ bottom: 20 })
Text('这是一个支持主题切换的示例应用')
.fontSize(16)
.fontColor(this.theme.isDark ? '#CCCCCC' : '#666666')
.margin({ bottom: 15 })
// 卡片组件
Column() {
Text('卡片标题')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(this.theme.textColor)
Text('卡片内容描述')
.fontSize(14)
.fontColor(this.theme.isDark ? '#AAAAAA' : '#999999')
.margin({ top: 10 })
}
.width('100%')
.padding(15)
.backgroundColor(this.theme.isDark ? '#2D2D2D' : '#F5F5F5')
.borderRadius(8)
}
.padding(20)
}
}
5.3 实战案例五:用户认证状态共享
// 用户信息接口
interface UserInfo {
name: string
avatar: string
isLoggedIn: boolean
}
// 全局认证状态管理
@Entry
@Component
struct AuthApp {
@Provide('userInfo') userInfo: UserInfo = {
name: '游客',
avatar: '👤',
isLoggedIn: false
}
build() {
Column() {
// 导航栏(显示用户信息)
NavBar()
// 主要内容
MainContent()
// 底部标签栏
TabBar()
}
.width('100%')
.height('100%')
}
}
// 导航栏组件
@Component
struct NavBar {
@Consume('userInfo') userInfo: UserInfo
build() {
Row() {
Text(this.userInfo.isLoggedIn ? `欢迎,${this.userInfo.name}` : '鸿蒙应用')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.width('100%')
.padding(15)
.backgroundColor('#1890FF')
.justifyContent(FlexAlign.Center)
}
}
// 主要内容组件
@Component
struct MainContent {
@Consume('userInfo') userInfo: UserInfo
@State username: string = ''
@State password: string = ''
build() {
Column() {
if (this.userInfo.isLoggedIn) {
// 已登录状态
Column() {
Text(`${this.userInfo.avatar}`)
.fontSize(64)
.margin({ bottom: 10 })
Text(`欢迎回来,${this.userInfo.name}!`)
.fontSize(24)
.margin({ bottom: 20 })
Button('退出登录')
.onClick(() => {
this.userInfo = {
name: '游客',
avatar: '👤',
isLoggedIn: false
}
})
}
} else {
// 未登录状态
Column() {
Text('请登录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 30 })
TextInput({ placeholder: '请输入用户名' })
.onChange((value: string) => {
this.username = value
})
.margin({ bottom: 15 })
TextInput({ placeholder: '请输入密码' })
.type(InputType.Password)
.onChange((value: string) => {
this.password = value
})
.margin({ bottom: 25 })
Button('登录')
.width('100%')
.onClick(() => {
if (this.username && this.password) {
// 模拟登录成功
this.userInfo = {
name: this.username,
avatar: '😊',
isLoggedIn: true
}
}
})
}
.padding(20)
}
}
.flexGrow(1)
.justifyContent(FlexAlign.Center)
.padding(20)
}
}
// 底部标签栏
@Component
struct TabBar {
@Consume('userInfo') userInfo: UserInfo
build() {
Row() {
Column() {
Text('🏠')
.fontSize(24)
Text('首页')
.fontSize(12)
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
Column() {
Text(this.userInfo.avatar)
.fontSize(24)
Text('我的')
.fontSize(12)
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
.width('100%')
.padding(10)
.backgroundColor('#FFFFFF')
.border({ width: { top: 1 }, color: '#EEEEEE' })
}
}
六、四大装饰器对比总结
6.1 核心特性对比
|
特性 |
@State |
@Prop |
@Link |
@Provide/@Consume |
|---|---|---|---|---|
|
数据流向 |
组件内部 |
父→子(单向) |
父↔子(双向) |
跨层级(双向) |
|
可修改性 |
可修改 |
只读 |
可修改 |
可修改 |
|
初始化方式 |
直接赋值 |
父组件赋值 |
$操作符 |
key匹配 |
|
适用场景 |
组件内部状态 |
子组件只读展示 |
父子双向交互 |
全局/跨层共享 |
|
性能影响 |
最小 |
较小 |
较小 |
中等 |
6.2 使用场景速查
|
场景 |
推荐装饰器 |
示例 |
|---|---|---|
|
组件内部状态 |
@State |
按钮点击次数、输入框内容、展开/收起状态 |
|
父传子(只读) |
@Prop |
列表项数据、配置信息、显示状态 |
|
父子双向绑定 |
@Link |
表单输入、开关状态、选中项 |
|
跨层级共享 |
@Provide/@Consume |
主题、语言、用户信息、全局配置 |
6.3 选择决策流程
需要管理状态?
↓
是 → 状态只在当前组件使用?
↓
是 → 使用 @State
↓
否 → 需要子组件修改吗?
↓
是 → 使用 @Link(父组件用$传递)
↓
否 → 使用 @Prop
↓
如果需要跨多个层级 → 使用 @Provide/@Consume
七、实战案例汇总
本文包含的5个完整实战案例:
|
序号 |
案例名称 |
核心装饰器 |
学习要点 |
|---|---|---|---|
|
1 |
计数器应用 |
@State |
基础状态管理、数组更新 |
|
2 |
商品列表 |
@Prop |
父子数据传递、只读特性 |
|
3 |
设置页面 |
@Link |
双向数据绑定、$操作符 |
|
4 |
主题切换 |
@Provide/@Consume |
跨层级共享、一对多 |
|
5 |
用户认证 |
@Provide/@Consume |
复杂状态共享、条件渲染 |
八、总结与最佳实践
8.1 核心要点回顾
-
@State:组件内部状态管理,支持基础类型和引用类型,引用类型需要整体赋值才能触发更新
-
@Prop:父子组件单向数据传递,子组件只能读取不能修改,父组件更新会同步到子组件
-
@Link:父子组件双向数据绑定,必须使用
$$操作符传递引用,任何一方修改都会同步 -
@Provide/@Consume:跨组件层级数据共享,无需逐层传递,支持一对多和双向绑定
8.2 最佳实践
-
优先使用@State:如果状态只在组件内部使用,始终优先选择@State
-
明确数据流向:需要子组件修改时用@Link,不需要修改时用@Prop
-
避免过度使用@Provide:只在真正需要跨层级共享时使用,过度使用会增加调试难度
-
对象整体赋值:修改对象或数组时,始终创建新对象/数组,而不是修改原数据
-
使用唯一key:在ForEach中使用唯一key,提升列表渲染性能
8.3 常见错误
// 错误1:对象属性直接修改
@State user: UserInfo = { name: '张三', age: 25 }
this.user.name = '李四' // 错误!不会触发UI更新
this.user = { ...this.user, name: '李四' } // 正确
// 错误2:@Link忘记使用$
ChildComponent({ count: this.count }) // 错误
ChildComponent({ count: $count }) // 正确
// 错误3:数组push不触发更新
@State list: string[] = []
this.list.push('item') // 错误
this.list = [...this.list, 'item'] // 正确
如果这篇文章对你有帮助,请点赞、收藏、关注支持!你的支持是我持续创作的动力!
有问题欢迎在评论区讨论,我会及时回复!
鸿蒙NEXT开发实战系列 -- 全部文章
-
第16篇:状态管理四大装饰器(当前)
标签:鸿蒙状态管理 | @State | @Prop | @Link | @Provide | @Consume | ArkUI | ArkTS | 声明式UI | 数据绑定 | HarmonyOS NEXT
更多推荐


所有评论(0)