ArkTS 中 @State、@Prop 和 @Link 的区别总结
ArkTS 状态管理装饰器对比摘要 核心区别 @State:组件私有状态,内部可修改并触发UI更新,必须初始化。适用于计数器、开关状态等组件内部数据管理。 @Prop:父向子单向传递数据,子组件只读不可修改。适用于展示型组件(如用户卡片、商品信息)的只读数据传递。 @Link:父子组件双向绑定,双方都可修改并同步更新,需用$传递引用。适用于表单等需要双向交互的场景。
ArkTS 中 @State、@Prop 和 @Link 的区别总结
📌 文档说明
本文档详细对比 ArkTS 中三个核心状态管理装饰器:@State、@Prop 和 @Link 的区别、使用场景和注意事项。适合用于:
- 理解 ArkTS 组件间通信机制
- 面试准备和技术交流
- 选择合适的状态管理方式
🎯 一、快速对比表
| 特性 | @State | @Prop | @Link |
|---|---|---|---|
| 作用域 | 组件内部 | 父 → 子 | 父 ↔ 子 |
| 数据流 | 私有状态 | 单向(只读) | 双向(可读可写) |
| 可修改 | ✅ 可以 | ❌ 不可以 | ✅ 可以 |
| 影响范围 | 当前组件 | 仅子组件 | 父子同步 |
| 传递方式 | - | 直接传值 | 使用 $ 传引用 |
| 使用场景 | 组件私有状态 | 展示型组件 | 表单、交互组件 |
| 初始化 | 必须赋初值 | 从父组件接收 | 从父组件接收 |
| 响应式 | ✅ 响应式 | ✅ 响应式 | ✅ 响应式 |
💡 二、核心概念
记忆口诀
@State → 我的数据我做主(私有状态)
@Prop → 只能看不能改(单向传递)
@Link → 大家一起用(双向绑定)
数据流向图
┌─────────────┐
│ @State │ 组件内部独享
│ (私有状态) │ ↓ 修改
│ │ ↓ 触发 UI 更新
└─────────────┘
┌─────────────┐
│ 父组件 │
│ @State data │
│ │
│ ↓ (传值)
│
│ 子组件 │
│ @Prop data │ ← 只读,不能修改
│ │
└─────────────┘
┌─────────────┐
│ 父组件 │
│ @State data │
│ ↕ (传引用 $)
│
│ 子组件 │
│ @Link data │ ← 可读可写,父子同步
│ │
└─────────────┘
📖 三、详细讲解
3.1 @State - 组件私有状态
定义
@State 装饰的变量是组件的私有状态,只在当前组件内部使用,修改会触发组件 UI 更新。
特点
- ✅ 组件私有:不能从外部传入
- ✅ 响应式更新:修改自动触发 UI 重新渲染
- ✅ 必须初始化:声明时必须赋初值
- ⚠️ 作用域限制:只影响当前组件
基本用法
@Entry
@Component
struct CounterDemo {
@State count: number = 0 // ✅ 必须赋初值
@State message: string = 'Hello'
@State isVisible: boolean = true
build() {
Column({ space: 10 }) {
Text(`计数: ${this.count}`)
Text(this.message)
Button('增加')
.onClick(() => {
this.count++ // ✅ 可以修改
this.message = 'World'
})
Button('切换显示')
.onClick(() => {
this.isVisible = !this.isVisible
})
if (this.isVisible) {
Text('显示内容')
}
}
}
}
使用场景
适合的场景:
- ✅ 组件内部的计数器、索引
- ✅ 开关状态(visible、loading、checked)
- ✅ 表单输入值(暂存在组件内)
- ✅ 列表数据(组件独有的数据)
- ✅ 临时状态(弹窗、Toast)
代码示例:
@Entry
@Component
struct TodoList {
@State todos: string[] = [] // 待办列表
@State inputText: string = '' // 输入框内容
@State loading: boolean = false // 加载状态
build() {
Column() {
// 输入框
TextInput({ text: this.inputText })
.onChange((value: string) => {
this.inputText = value
})
// 添加按钮
Button('添加')
.onClick(() => {
if (this.inputText) {
this.todos.push(this.inputText)
this.inputText = ''
}
})
// 列表
if (this.loading) {
LoadingProgress()
} else {
List() {
ForEach(this.todos, (todo: string) => {
ListItem() {
Text(todo)
}
})
}
}
}
}
}
3.2 @Prop - 父向子单向传递
定义
@Prop 实现父组件向子组件的单向数据传递,子组件只能读取不能修改,父组件数据变化时子组件自动更新。
特点
- ✅ 单向数据流:父 → 子
- ❌ 只读属性:子组件不能修改
- ✅ 自动更新:父组件变化,子组件自动更新
- ✅ 传值方式:直接传值,不需要
$ - ⚠️ 修改报错:尝试修改会编译错误
基本用法
// ========== 子组件 ==========
@Component
struct UserCard {
@Prop username: string // 从父组件接收
@Prop age: number
@Prop avatar: string
build() {
Column({ space: 10 }) {
Image(this.avatar)
.width(80)
.height(80)
.borderRadius(40)
Text(this.username)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`年龄: ${this.age}`)
.fontSize(14)
.fontColor(Color.Gray)
Button('尝试修改')
.onClick(() => {
// ❌ 错误:不能修改 @Prop
// this.username = 'New Name' // 编译错误!
console.log('子组件不能修改 @Prop')
})
}
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
}
}
// ========== 父组件 ==========
@Entry
@Component
struct UserProfile {
@State userName: string = 'Tom'
@State userAge: number = 20
@State userAvatar: string = ''
build() {
Column({ space: 20 }) {
Text('用户资料')
.fontSize(24)
.fontWeight(FontWeight.Bold)
// 传递给子组件(直接传值)
UserCard({
username: this.userName, // ✅ 直接传值
age: this.userAge,
avatar: this.userAvatar
})
Button('修改用户信息')
.onClick(() => {
this.userName = 'Jerry' // ✅ 父组件修改
this.userAge = 25
// 子组件自动更新
})
}
.width('100%')
.padding(20)
}
}
使用场景
适合的场景:
- ✅ 展示型组件(卡片、列表项、头像)
- ✅ 只读数据(商品信息、用户资料、文章详情)
- ✅ 静态配置(主题色、字体大小、样式参数)
- ✅ 纯 UI 组件(按钮、标签、徽章)
完整示例:商品卡片
// 商品数据接口
interface Product {
id: string
name: string
price: number
image: string
stock: number
}
// ========== 子组件:商品卡片 ==========
@Component
struct ProductCard {
@Prop productName: string
@Prop price: number
@Prop imageUrl: string
@Prop stock: number
build() {
Column({ space: 10 }) {
// 商品图片
Image(this.imageUrl)
.width('100%')
.height(200)
.borderRadius(8)
.backgroundColor('#F5F5F5')
// 商品名称
Text(this.productName)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 价格和库存
Row() {
Text(`¥${this.price}`)
.fontSize(20)
.fontColor('#FF4D4F')
.fontWeight(FontWeight.Bold)
Spacer()
Text(`库存: ${this.stock}`)
.fontSize(14)
.fontColor(Color.Gray)
}
.width('100%')
// 购买按钮
Button('立即购买')
.width('100%')
.backgroundColor('#FF4D4F')
}
.width('100%')
.padding(15)
.backgroundColor(Color.White)
.borderRadius(12)
}
}
// ========== 父组件:商品列表 ==========
@Entry
@Component
struct ProductList {
@State products: Product[] = [
{ id: '1', name: 'iPhone 15 Pro', price: 7999, stock: 100, image: '' },
{ id: '2', name: 'iPad Pro', price: 6799, stock: 50, image: '' },
{ id: '3', name: 'MacBook Pro', price: 12999, stock: 30, image: '' }
]
build() {
Scroll() {
Column({ space: 15 }) {
Text('商品列表')
.fontSize(24)
.fontWeight(FontWeight.Bold)
// 使用 ForEach 渲染商品列表
ForEach(this.products, (product: Product) => {
ProductCard({
productName: product.name,
price: product.price,
imageUrl: product.image,
stock: product.stock
})
}, (product: Product) => product.id)
}
.width('100%')
.padding(15)
}
}
}
3.3 @Link - 父子双向同步
定义
@Link 实现父子组件的双向数据绑定,子组件可以修改数据,修改会同步到父组件。
特点
- ✅ 双向绑定:父 ↔ 子,数据同步
- ✅ 子组件可写:子组件可以修改数据
- ✅ 父子同步:任一方修改,另一方自动更新
- ⚠️ 传引用:必须使用
$符号传递引用 - ⚠️ 不能初始化:不能在子组件中赋初值
基本用法
// ========== 子组件 ==========
@Component
struct Counter {
@Link count: number // ✅ 双向绑定
build() {
Column({ space: 10 }) {
Text('子组件')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`计数: ${this.count}`)
.fontSize(24)
Button('子组件增加')
.onClick(() => {
this.count++ // ✅ 可以修改,父组件会同步
})
Button('子组件减少')
.onClick(() => {
this.count--
})
}
.padding(20)
.backgroundColor('#E3F2FD')
.borderRadius(12)
}
}
// ========== 父组件 ==========
@Entry
@Component
struct ParentPage {
@State parentCount: number = 0 // 父组件状态
build() {
Column({ space: 20 }) {
Text('父组件')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(`父组件看到的计数: ${this.parentCount}`)
.fontSize(16)
// 使用 $ 传递引用
Counter({ count: $parentCount }) // ⚠️ 注意 $ 符号
Button('父组件增加')
.onClick(() => {
this.parentCount += 10 // ✅ 父组件修改,子组件同步
})
Button('父组件重置')
.onClick(() => {
this.parentCount = 0
})
}
.width('100%')
.padding(20)
}
}
使用场景
适合的场景:
- ✅ 表单输入组件(TextInput、DatePicker)
- ✅ 开关切换(Toggle、Checkbox、Radio)
- ✅ 可编辑列表(增删改查)
- ✅ 双向绑定控件(Slider、Rating)
- ✅ 需要父子协作的组件
完整示例:表单组件
// ========== 子组件:输入框 ==========
@Component
struct FormInput {
@Link value: string // 双向绑定
private label: string = ''
private placeholder: string = ''
build() {
Column({ space: 5 }) {
Text(this.label)
.fontSize(14)
.fontWeight(FontWeight.Medium)
TextInput({ text: this.value, placeholder: this.placeholder })
.onChange((text: string) => {
this.value = text // ✅ 子组件修改,父组件同步
})
.width('100%')
.height(40)
}
.alignItems(HorizontalAlign.Start)
.width('100%')
}
}
// ========== 子组件:开关 ==========
@Component
struct FormSwitch {
@Link checked: boolean // 双向绑定
private label: string = ''
build() {
Row() {
Text(this.label)
.fontSize(14)
.layoutWeight(1)
Toggle({ type: ToggleType.Switch, isOn: this.checked })
.onChange((isOn: boolean) => {
this.checked = isOn // ✅ 子组件修改,父组件同步
})
}
.width('100%')
.padding(10)
}
}
// ========== 父组件:注册表单 ==========
@Entry
@Component
struct RegistrationForm {
@State username: string = ''
@State email: string = ''
@State password: string = ''
@State agreeTerms: boolean = false
// 提交表单
handleSubmit() {
if (!this.username || !this.email || !this.password) {
AlertDialog.show({
message: '请填写完整信息',
confirm: { value: '确定' }
})
return
}
if (!this.agreeTerms) {
AlertDialog.show({
message: '请同意服务条款',
confirm: { value: '确定' }
})
return
}
console.log('提交数据:', {
username: this.username,
email: this.email,
password: this.password
})
AlertDialog.show({
message: '注册成功!',
confirm: { value: '确定' }
})
}
build() {
Scroll() {
Column({ space: 20 }) {
Text('用户注册')
.fontSize(28)
.fontWeight(FontWeight.Bold)
// 使用 @Link 组件
FormInput({
value: $username, // ⚠️ 使用 $ 传引用
label: '用户名',
placeholder: '请输入用户名'
})
FormInput({
value: $email,
label: '邮箱',
placeholder: '请输入邮箱'
})
FormInput({
value: $password,
label: '密码',
placeholder: '请输入密码'
})
FormSwitch({
checked: $agreeTerms,
label: '我已阅读并同意服务条款'
})
// 显示表单数据(父组件可以看到子组件的修改)
Column({ space: 5 }) {
Text('表单数据预览:')
.fontSize(14)
.fontColor(Color.Gray)
Text(`用户名: ${this.username}`)
Text(`邮箱: ${this.email}`)
Text(`密码: ${'*'.repeat(this.password.length)}`)
Text(`同意条款: ${this.agreeTerms ? '是' : '否'}`)
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(10)
.backgroundColor('#F5F5F5')
.borderRadius(8)
Button('注册')
.width('100%')
.height(44)
.fontSize(16)
.backgroundColor('#007DFF')
.onClick(() => this.handleSubmit())
}
.width('100%')
.padding(20)
}
}
}
🔄 四、三者对比示例
让我们通过一个完整的示例来看三者的区别:
// ========== 子组件定义 ==========
// 1. 使用 @Prop(只读)
@Component
struct DisplayCard {
@Prop title: string
@Prop count: number
build() {
Column({ space: 10 }) {
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`${this.count}`)
.fontSize(24)
.fontColor('#007DFF')
Button('子组件尝试修改')
.onClick(() => {
// ❌ 不能修改 @Prop
// this.count++ // 编译错误
console.log('不能修改 @Prop')
})
}
.width('100%')
.padding(15)
.backgroundColor('#F0F0F0')
.borderRadius(8)
}
}
// 2. 使用 @Link(可读可写)
@Component
struct EditableCard {
@Link count: number // 双向绑定
build() {
Column({ space: 10 }) {
Text('可编辑卡片')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`${this.count}`)
.fontSize(24)
.fontColor('#FF4D4F')
Button('子组件增加')
.onClick(() => {
this.count++ // ✅ 可以修改,父组件同步
})
Button('子组件减少')
.onClick(() => {
this.count--
})
}
.width('100%')
.padding(15)
.backgroundColor('#E3F2FD')
.borderRadius(8)
}
}
// ========== 父组件 ==========
@Entry
@Component
struct ComparisonDemo {
@State privateCount: number = 0 // ① 组件私有状态
@State propCount: number = 0 // ② 用于 @Prop 的状态
@State linkCount: number = 0 // ③ 用于 @Link 的共享状态
build() {
Scroll() {
Column({ space: 30 }) {
Text('@State、@Prop、@Link 对比')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Divider()
// ========== @State 示例 ==========
Column({ space: 10 }) {
Text('1️⃣ @State - 组件私有状态')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FF6B6B')
Text(`私有计数: ${this.privateCount}`)
.fontSize(16)
Button('修改私有状态')
.onClick(() => {
this.privateCount++ // ✅ 只影响当前组件
})
Text('特点:组件内部独享,修改只影响当前组件')
.fontSize(12)
.fontColor(Color.Gray)
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(15)
.backgroundColor('#FFE5E5')
.borderRadius(12)
Divider()
// ========== @Prop 示例 ==========
Column({ space: 10 }) {
Text('2️⃣ @Prop - 单向传递(父 → 子)')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#4ECDC4')
Text(`父组件的值: ${this.propCount}`)
.fontSize(16)
// 子组件(@Prop)
DisplayCard({
title: '只读卡片',
count: this.propCount // ✅ 直接传值
})
Button('父组件修改')
.onClick(() => {
this.propCount++ // 父组件修改,子组件自动更新
})
Text('特点:子组件只读,父组件修改会同步')
.fontSize(12)
.fontColor(Color.Gray)
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(15)
.backgroundColor('#E0F7F6')
.borderRadius(12)
Divider()
// ========== @Link 示例 ==========
Column({ space: 10 }) {
Text('3️⃣ @Link - 双向绑定(父 ↔ 子)')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#95E1D3')
Text(`父组件看到的值: ${this.linkCount}`)
.fontSize(16)
// 子组件(@Link)
EditableCard({
count: $linkCount // ⚠️ 使用 $ 传引用
})
Button('父组件修改')
.onClick(() => {
this.linkCount += 10 // 父子同步
})
Button('父组件重置')
.onClick(() => {
this.linkCount = 0
})
Text('特点:子组件可修改,父子双向同步')
.fontSize(12)
.fontColor(Color.Gray)
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(15)
.backgroundColor('#E8F8F5')
.borderRadius(12)
Divider()
// ========== 总结 ==========
Column({ space: 10 }) {
Text('📝 总结')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text('• @State: 我的数据我做主(私有状态)')
.fontSize(14)
Text('• @Prop: 只能看不能改(单向传递)')
.fontSize(14)
Text('• @Link: 大家一起用(双向绑定)')
.fontSize(14)
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(15)
.backgroundColor('#FFF9E6')
.borderRadius(12)
}
.width('100%')
.padding(20)
}
}
}
⚠️ 五、常见错误与注意事项
5.1 错误 1:@Prop 尝试修改
@Component
struct Child {
@Prop value: string
build() {
Button('修改')
.onClick(() => {
// ❌ 编译错误!@Prop 是只读的
this.value = 'new value'
})
}
}
错误信息:
Property 'value' is read-only
正确做法:
如果需要修改,应该使用 @Link 或通过回调函数通知父组件:
@Component
struct Child {
@Prop value: string
private onChange?: (newValue: string) => void // 回调函数
build() {
Button('修改')
.onClick(() => {
// ✅ 通过回调通知父组件
if (this.onChange) {
this.onChange('new value')
}
})
}
}
@Entry
@Component
struct Parent {
@State parentValue: string = 'old'
build() {
Child({
value: this.parentValue,
onChange: (newValue: string) => {
this.parentValue = newValue // 父组件修改
}
})
}
}
5.2 错误 2:@Link 忘记使用 $
@Entry
@Component
struct Parent {
@State count: number = 0
build() {
Column() {
// ❌ 错误:直接传值
Child({ count: this.count })
// ✅ 正确:使用 $ 传引用
Child({ count: $count })
}
}
}
错误现象:
- 子组件修改不会同步到父组件
- 父组件修改也不会同步到子组件
- 失去了双向绑定的效果
记忆技巧:
// @Prop:直接传值(单向)
Child({ value: this.data });
// @Link:使用 $ 传引用(双向)
Child({ value: $data }); // $ = 引用
5.3 错误 3:混淆使用场景
// ❌ 错误:展示型组件不需要 @Link
@Component
struct ProductCard {
@Link productName: string // 只是展示,不需要双向绑定
@Link price: number
}
// ✅ 正确:展示型组件用 @Prop
@Component
struct ProductCard {
@Prop productName: string
@Prop price: number
}
// ❌ 错误:表单组件用 @Prop
@Component
struct InputField {
@Prop value: string // 用户输入无法同步到父组件
}
// ✅ 正确:表单组件用 @Link
@Component
struct InputField {
@Link value: string // 用户输入可以同步到父组件
}
5.4 错误 4:@State 初始化问题
@Component
struct Demo {
// ❌ 错误:@State 必须初始化
@State count: number
// ✅ 正确:必须赋初值
@State count: number = 0
}
5.5 错误 5:@Link 不能初始化
@Component
struct Child {
// ❌ 错误:@Link 不能赋初值
@Link count: number = 0
// ✅ 正确:从父组件接收
@Link count: number
}
📊 六、选择指南
6.1 决策树
需要状态管理?
├─ 是否需要从父组件接收?
│ ├─ 否 → 使用 @State
│ │ (组件私有状态)
│ │
│ └─ 是 → 子组件是否需要修改?
│ ├─ 否 → 使用 @Prop
│ │ (只读,展示型组件)
│ │
│ └─ 是 → 使用 @Link
│ (可写,表单组件)
│
└─ 否 → 使用普通变量
6.2 使用场景对照表
| 场景 | 装饰器 | 说明 |
|---|---|---|
| 计数器(组件内部) | @State | 组件私有状态 |
| Loading 状态 | @State | 组件控制 |
| 弹窗显示/隐藏 | @State | 组件内部控制 |
| 商品卡片展示 | @Prop | 只读,展示商品信息 |
| 用户头像组件 | @Prop | 只读,展示用户信息 |
| 文章列表项 | @Prop | 只读,展示文章信息 |
| TextInput 输入框 | @Link | 需要同步输入值 |
| Checkbox 复选框 | @Link | 需要同步选中状态 |
| Toggle 开关 | @Link | 需要同步开关状态 |
| 可编辑列表 | @Link | 需要同步编辑结果 |
| Slider 滑块 | @Link | 需要同步滑块值 |
🎤 七、面试回答模板
7.1 基础版本(30 秒)
"@State、@Prop 和 @Link 是 ArkTS 中三种不同的状态管理装饰器:
- @State 是组件的私有状态,只在当前组件内使用,修改会触发 UI 更新
- @Prop 实现父向子单向传递,子组件只能读取不能修改,父组件变化时子组件自动更新
- @Link 实现父子双向绑定,子组件修改会同步到父组件,需要用
$符号传递引用简单来说:@State 是自己用,@Prop 是看别人的,@Link 是和别人共享。"
7.2 进阶版本(1-2 分钟)
"从数据流向来看:
@State 是组件的内部状态,完全由组件自己控制,比如一个计数器的 count 值。修改它只会影响当前组件的 UI。
@Prop 实现单向数据流:父 → 子。它像一个只读属性,子组件接收父组件传来的值,可以使用但不能修改。如果父组件的值变了,子组件会自动更新。比如商品卡片组件接收商品信息,只用来展示。传递时直接传值:
Child({ value: this.data })。@Link 实现双向绑定:父 ↔ 子。子组件可以修改这个值,修改后会同步到父组件。传递时需要用
$符号传引用而不是传值:Child({ value: $data })。比如表单输入组件,子组件的输入要同步给父组件。使用场景上:
- 组件内部的临时状态用 @State
- 展示型组件用 @Prop
- 表单、可编辑组件用 @Link"
7.3 深度版本(追问)
如果问:为什么 @Link 需要 $ 符号?
"$ 符号表示传递的是引用而不是值。
在 JavaScript 中,基本类型(number、string、boolean)是值传递,对象是引用传递。但在 ArkTS 中,为了明确表示’这是一个需要双向绑定的引用’,使用
$符号来标记。当你使用
$data时,传递的是 data 的引用,子组件和父组件指向同一个数据源。子组件修改时,实际上是修改了共享的数据,所以父组件也能看到变化。如果不用
$,传递的就是值的拷贝,子组件修改只是修改自己的拷贝,不会影响父组件。"
如果问:@Prop 为什么是只读的?
"这是为了保持单向数据流的设计原则。
单向数据流的好处是:
- 数据流向清晰:数据总是从父组件流向子组件,便于追踪
- 避免混乱:如果子组件能随意修改,数据来源就不明确了
- 便于调试:出问题时只需要检查父组件,不用担心子组件偷偷修改
如果确实需要子组件修改数据,有两种方式:
- 使用 @Link 实现双向绑定
- 通过回调函数通知父组件修改,保持单向数据流
这种设计在 React、Vue 等主流框架中也是一样的。"
🎯 八、核心要点总结
8.1 记忆口诀
@State → 我的数据我做主(私有状态)
@Prop → 只能看不能改(单向传递)
@Link → 大家一起用(双向绑定)
8.2 传递方式
// @State:不需要传递
@State data: string = 'value'
// @Prop:直接传值
Child({ data: this.data })
// @Link:使用 $ 传引用
Child({ data: $data }) // 注意 $ 符号
8.3 使用决策
第一步:问自己"数据从哪来?"
- 组件内部产生 →
@State - 父组件传入 → 继续第二步
第二步:问自己"需要修改吗?"
- 不需要修改(只展示) →
@Prop - 需要修改(可编辑) →
@Link
8.4 对比总结
| 装饰器 | 数据流 | 可修改 | 传递方式 | 典型场景 |
|---|---|---|---|---|
| @State | 组件内部 | ✅ 是 | - | 计数器、状态 |
| @Prop | 父 → 子 | ❌ 否 | 直接传值 | 商品卡片、头像 |
| @Link | 父 ↔ 子 | ✅ 是 | $ 传引用 |
表单、开关 |
📚 九、最佳实践
9.1 优先使用 @State + @Prop
原则:优先使用单向数据流(@State + @Prop),只在必要时使用 @Link。
原因:
- 单向数据流更清晰,易于理解和维护
- 避免复杂的双向依赖关系
- 便于调试和测试
// ✅ 推荐:单向数据流
@Entry
@Component
struct Parent {
@State data: string = 'value'
handleChange(newValue: string) {
this.data = newValue
}
build() {
Child({
value: this.data,
onChange: (val) => this.handleChange(val)
})
}
}
// ⚠️ 谨慎使用:双向绑定
@Entry
@Component
struct Parent {
@State data: string = 'value'
build() {
Child({ value: $data })
}
}
9.2 组件封装建议
展示型组件:使用 @Prop
@Component
export struct ProductCard {
@Prop name: string
@Prop price: number
@Prop image: string
build() {
// 只展示,不修改
}
}
容器型组件:使用 @State
@Entry
@Component
struct ProductList {
@State products: Product[] = []
build() {
List() {
ForEach(this.products, (product: Product) => {
ProductCard({
name: product.name,
price: product.price,
image: product.image
})
})
}
}
}
表单组件:使用 @Link
@Component
export struct CustomInput {
@Link value: string
private label: string = ''
build() {
Column() {
Text(this.label)
TextInput({ text: this.value })
.onChange((text) => {
this.value = text
})
}
}
}
9.3 命名规范
// @State:描述性名称
@State isLoading: boolean = false
@State userList: User[] = []
@State currentIndex: number = 0
// @Prop:与数据含义一致
@Prop productName: string
@Prop userName: string
@Prop itemCount: number
// @Link:强调共享性
@Link selectedItems: string[]
@Link formData: FormData
@Link sharedCount: number
✅ 检查清单
在使用装饰器前,问自己这些问题:
@State 检查清单
- 这个状态只在当前组件使用吗?
- 不需要从父组件接收吗?
- 已经赋初值了吗?
@Prop 检查清单
- 这个数据从父组件接收吗?
- 子组件只需要展示,不需要修改吗?
- 父组件变化时,子组件需要更新吗?
- 传递时直接传值了吗(没有用 $)?
@Link 检查清单
- 这个数据需要父子同步吗?
- 子组件需要修改数据吗?
- 修改需要同步到父组件吗?
- 传递时使用 $ 符号了吗?
🎬 总结
一句话总结:
@State 管理组件内部状态,@Prop 实现父向子单向传递,@Link 实现父子双向绑定。三者的选择取决于数据的来源和使用方式。
核心原则:
- 能用 @State 就用 @State(组件私有)
- 优先使用 @Prop(单向数据流)
- 必要时才用 @Link(双向绑定)
记住:正确使用这三个装饰器,能让你的代码更清晰、更易维护!
祝你开发顺利! 🚀
更多推荐



所有评论(0)