ArkTS 进阶之道(27):状态管理装饰器全景边界——V1/V2 双轨根因 @Component vs @ComponentV2

ArkTS 进阶之道(27):状态管理装饰器全景边界——V1/V2 双轨根因 @Component vs @ComponentV2

本文是「ArkTS 进阶之道」系列第 27 篇,续「ArkUI 状态联动」深水区。上篇讲 @LocalStorageLink/@LocalStorageProp 页面级状态绑定边界(双向绑定 vs 单向只读)。本文讲状态管理装饰器全景边界:V1/V2 双轨根因——@Component V1 装饰器体系 vs @ComponentV2 V2 装饰器体系。能力系列篇 27 讲过 V1/V2 怎么用,本文讲为哈 V1/V2 双轨合法 @Component V1 装饰器体系不兼容 V2——根因在双轨装饰器体系边界。

一、开篇:状态管理装饰器不是单轨,是 V1/V2 双轨体系

你写 React 时,状态管理是「hooks 魔法」(useState/useEffect/useMemo 钩子):

// React 状态管理:hooks 魔法(useState/useEffect/useMemo 钩子) function Comp() { const [count, setCount] = useState(0) // useState 钩子 const [theme, setTheme] = useState('#007DFF') // useState 钩子 useEffect(() => { console.log('count 变', count) }, [count]) // useEffect 钩子 return <button onClick={() => setCount(count + 1)}>count={count}</button> } // React 状态管理:hooks 魔法(useState/useEffect/useMemo 钩子,单轨)

你写鸿蒙 ArkTS 时,状态管理是V1/V2 双轨体系——@Component V1 装饰器体系 + @ComponentV2 V2 装饰器体系:

// ✅ V1 路径:@Component V1 装饰器体系(@State/@Prop/@Link/@Provide/@Consume) @Component struct V1Comp { @State count: number = 0 // ✅ V1 @State 本地状态 @Provide('theme') theme: string = '#007DFF' // ✅ V1 @Provide 跨层级传递 build() { Button(`V1 count=${this.count}`).onClick(() => { this.count++ }) } } // ✅ V2 路径:@ComponentV2 V2 装饰器体系(@Param/@Once/@Event/@Provider/@Consumer) @ComponentV2 struct V2Comp { @Param count: number = 0 // ✅ V2 @Param 本地状态(替代 V1 @State) @Provider('theme') theme: string = '#007DFF' // ✅ V2 @Provider �跨层级传递(替代 V1 @Provide) build() { Button(`V2 count=${this.count}`).onClick(() => { this.count++ }) } } // 状态管理 V1/V2 双轨:@Component V1 体系 + @ComponentV2 V2 体系(双轨装饰器体系)

hooks 魔法 vs 双轨装饰器体系的区别:React 把状态管理当 hooks 魔法(useState/useEffect/useMemo 钩子单轨),ArkTS 把状态管理当「V1/V2 双轨装饰器体系」(@Component V1 体系 + @ComponentV2 V2 体系双轨)。根因不是 hooks 魔法是双轨装饰器体系——V1/V2 双轨,@Component V1 装饰器体系 + @ComponentV2 V2 装饰器体系双轨。

二、根因:V1/V2 双轨装饰器体系的机制

鸿蒙 ArkUI 的状态管理是V1/V2 双轨装饰器体系绑定——@Component V1 装饰器体系不兼容 V2,@ComponentV2 V2 装饰器体系不兼容 V1,来自三重绑定机制。

机制 1:V1 装饰器体系——@Component �装的 struct 只能用 V1 装饰器

V1 装饰器体系**@Component 装的 struct 只能用 V1 装饰器**——@State/@Prop/@Link/@Provide/@Consume/@Watch/@Observed/@ObjectLink/AppStorage/LocalStorage 全是 V1:

// ✅ V1 装饰器体系:@Component 装的 struct 只能用 V1 装饰器 @Component struct V1Comp { @State count: number = 0 // ✅ V1 @State 本地状态 @Prop title: string = '' // ✅ V1 @Prop 单向只读 @Link synced: number // ✅ V1 @Link 双向绑定 @Provide('theme') theme: string = '#007DFF' // ✅ V1 @Provide 跨层级传递 @Consume('appTheme') appTheme: string // ✅ V1 @Consume 跨层级消费 @Watch('onCountChange') @State watched: number = 0 // ✅ V1 @Watch 副作用回调 @ObjectLink item: ObservedItem // ✅ V1 @ObjectLink 嵌套对象引用 build() { Column() { Button(`V1 count=${this.count}`).onClick(() => { this.count++ }) } } } // V1 装饰器体系:@Component 装的 struct 只能用 V1 装饰器(@State/@Prop/@Link/@Provide/@Consume/@Watch/@ObjectLink)

V1 装饰器体系:V1@Component装的 struct 只能用 V1 装饰器——@State/@Prop/@Link/@Provide/@Consume/@Watch/@Observed/@ObjectLink/AppStorage/LocalStorage 全是 V1。根因不是混用是 V1 装饰器体系——@Component �装的 struct 只能用 V1 装饰器,V1 体系全套装饰器。

机制 2:V2 装饰器体系——@ComponentV2 �装的 struct 只能用 V2 装饰器

V2 装饰器体系**@ComponentV2 �装的 struct 只能用 V2 装饰器**——@Param/@Once/@Event/@Provider/@Consumer/@Computed 全是 V2:

// ✅ V2 脚饰器体系:@ComponentV2 装的 struct 只能用 V2 脚饰器 @ComponentV2 struct V2Comp { @Param count: number = 0 // ✅ V2 @Param 本地状态(替代 V1 @State) @Param({ once }) title: string = '' // ✅ V2 @Once 只同步一次(替代 V1 @Prop) @Event onCountChange: (n: number) => void = () => {} // ✅ V2 @Event 事件回调(替代 V1 @Link) @Provider('theme') theme: string = '#007DFF' // ✅ V2 @Provider 跨层级传递(替代 V1 @Provide) @Consumer('appTheme') appTheme: string // ✅ V2 @Consumer 跨层级消费(替代 V1 @Consume) @Computed get doubled(): number { return this.count * 2 } // ✅ V2 @Computed 计算属性(替代 V1 @Watch) build() { Column() { Button(`V2 count=${this.count}`).onClick(() => { this.count++ }) } } } // V2 脚饰器体系:@ComponentV2 装的 struct 只能用 V2 脚饰器(@Param/@Once/@Event/@Provider/@Consumer/@Computed)

V2 脚饰器体系:V2@ComponentV2装的 struct 只能用 V2 脚饰器——@Param/@Once/@Event/@Provider/@Consumer/@Computed 全是 V2。根因不是混用是 V2 脚饰器体系——@ComponentV2 装的 struct 只能用 V2 脚饰器,V2 体系全套脚饰器。

机制 3:V1 vs V2 装饰器映射边界——同名功能不同名

V1 vs V2 脚饰器映射边界同名功能不同名——V1 @State → V2 @Param,V1 @Prop → V2 @Once,V1 @Link → V2 @Event,V1 @Provide → V2 @Provider,V1 @Consume → V2 @Consumer,V1 @Watch → V2 @Computed:

// ✅ V1 vs V2 脚饰器映射边界:同名功能不同名 // V1 @State 本地状态 → V2 @Param 本地状态 @Component struct V1StateComp { @State count: number = 0; build() {} } @ComponentV2 struct V2ParamComp { @Param count: number = 0; build() {} } // V1 @Prop 单向只读 → V2 @Once 只同步一次 @Component struct V1PropComp { @Prop title: string = ''; build() {} } @ComponentV2 struct V2OnceComp { @Param({ once }) title: string = ''; build() {} } // V1 @Link 双向绑定 → V2 @Event 事件回调 @Component struct V1LinkComp { @Link synced: number; build() {} } @ComponentV2 struct V2EventComp { @Event onSync: (n: number) => void = () => {}; build() {} } // V1 @Provide 跨层级传递 → V2 @Provider 跨层级传递 @Component struct V1ProvideComp { @Provide('theme') theme: string = '#007DFF'; build() {} } @ComponentV2 struct V2ProviderComp { @Provider('theme') theme: string = '#007DFF'; build() {} } // V1 @Watch �副作用回调 → V2 @Computed 计算属性 @Component struct V1WatchComp { @Watch('onCount') @State count: number = 0; build() {} } @ComponentV2 struct V2ComputedComp { @Param count: number = 0; @Computed get doubled(): number { return this.count * 2 }; build() {} } // V1 vs V2 脚饰器映射边界:同名功能不同名(@State→@Param, @Prop→@Once, @Link→@Event, @Provide→@Provider, @Consume→@Consumer, @Watch→@Computed)

V1 vs V2 脚饰器映射边界:V1 vs V2 脚饰器映射——同名功能不同名(V1 @State → V2 @Param,V1 @Prop → V2 @Once,V1 @Link → V2 @Event,V1 @Provide → V2 @Provider,V1 @Consume → V2 @Consumer,V1 @Watch → V2 @Computed)。根因不是同名是同名功能不同名——V1 vs V2 装饰器映射,同名功能不同名。

机制 4:V1/V2 不兼容边界——@Component 不能用 V2 脚饰器,@ComponentV2 不能用 V1 脚饰器

V1/V2 不兼容边界**@Component 不能用 V2 装饰器,@ComponentV2 不能用 V1 脚饰器**——编译报错:

// ❌ @Component 不能用 V2 脚饰器(编译报错) @Component struct BadV1Comp { @Param count: number = 0 // ❌ 编译报错(@Component 只能用 V1 脚饰器,@Param 是 V2) build() {} } // ❌ @ComponentV2 不能用 V1 脚饰器(编译报错) @ComponentV2 struct BadV2Comp { @State count: number = 0 // ❌ 编译报错(@ComponentV2 只能用 V2 脚饰器,@State 是 V1) build() {} } // V1/V2 不兼容边界:@Component 不能用 V2 装饰器,@ComponentV2 不能用 V1 装饰器(编译报错)

V1/V2 不兼容边界:V1/V2 不兼容——@Component 不能用 V2 装饰器(@Param 编译报错),@ComponentV2 不能用 V1 脚饰器(@State 编译报错)。根因不是混用是 V1/V2 不兼容边界——@Component 只能用 V1 装饰器,@ComponentV2 只能用 V2 脚饰器,混用编译报错。

三、真机配图:状态管理装饰器全景边界——V1 路径全套装饰器同步

初始态(V1 装饰器体系全套 @State localCount=0、@Provide theme=“#007DFF” 葝色、@ObjectLink item.count=0 均 V1 路径初始态齐):

点调三按钮后(@State localCount=1、@Provide theme=“#FF4D4F” 红色文字、@ObjectLink item.count=1 均 V1 路径全套装饰器同步证据齐):

对比证据:点改 @State localCount++ 后 localCount=1 刷 UI(V1 @State 本地状态赋值刷 UI),点改 @Provide theme=红色后 theme=“#FF4D4F” 红色文字同步(V1 @Provide 跨层级传递祖辈定义后辈消费),点改 @ObjectLink item.count++ 后 item.count=1 刷 UI(V1 @ObjectLink 嵌套对象引用子属性改刷 UI)。V1 装饰器体系全套同步——@State/@Provide/@ObjectLink 均 V1 路径装饰器体系全套同步,V2 路径对比表格讲根因(@ComponentV2 API 23+ 真机 API 21 跑不动)。

四、V1 vs V2 装饰器全景对照表

V1 装饰器(@Component)V2 装饰器(@ComponentV2)功能边界
@State@Param本地状态:赋值刷 UI 依赖追踪
@Prop@Param({ once }) / @Once单向只读:父改子同步子不改
@Link@Event双向绑定:父改子同步子改父同步
@Provide@Provider跨层级传递:祖辈定义后辈消费
@Consume@Consumer跨层级消费:后辈消费祖辈状态
@Watch@Computed副作用/计算属性:变触发回调
@Observed@Observed(V2 兼容)标记类可观测:嵌套对象子属性改触发
@ObjectLink@Param(V2 接收 @Observed)嵌套对象引用:子属性改刷子组件 UI
AppStorage + @StorageLink@Param(V2 不用 AppStorage)应用级状态:所有页面共享
LocalStorage + @LocalStorageLink@Param(V2 不用 LocalStorage)页面级状态:同页面多组件共享

全景对照核心:V1 用 AppStorage/LocalStorage 存储槽 + @StorageLink/@LocalStorageLink 绑定,V2 直接用 @Param(V2 不用 AppStorage/LocalStorage 存储槽,V2 状态都绑在 @Param)。V1 @Watch 副作用回调 vs V2 @Computed 计算属性(V2 用 @Computed 替代 @Watch,计算属性自动追踪依赖)。V1 @Link 双向绑定 vs V2 @Event 事件回调(V2 用 @Event 替代 @Link,子改父用事件回调不双向绑定)。

五、真解法:V1/V2 双轨选用的三个场景

场景 1:V1 路径首选(90% 场景首选,@Component V1 装饰器体系)

// ✅ V1 路径:@Component V1 装饰器体系(首选,90% 场景) @Component struct V1Comp { @State count: number = 0 // ✅ V1 @State 本地状态 @Provide('theme') theme: string = '#007DFF' // ✅ V1 @Provide 跨层级传递 @ObjectLink item: ObservedItem // ✅ V1 @ObjectLink 嵌套对象引用 build() { Column() { Text(`V1 count=${this.count}`).fontColor(this.theme) Button('改 count++').onClick(() => { this.count++ }) } } } // V1 路径首选:@Component V1 装饰器体系(@State/@Prop/@Link/@Provide/@Consume/@Watch/@ObjectLink 全套)

为哈能跑:V1 路径首选——@Component V1 装饰器体系全套(@State/@Prop/@Link/@Provide/@Consume/@Watch/@Observed/@ObjectLink/AppStorage/LocalStorage),API 21+ 全兼容,90% 场景用 V1 就够。首选这个,90% 的场景用 V1 装饰器体系就够。要写「状态管理(本地/跨层级/嵌套/应用级/页面级等)」时用这个——V1 装饰器体系全套兼容,避坑 V2 API 23+ 限制。

场景 2:V2 路径新项目(API 23+ 新项目用,@ComponentV2 V2 装饰器体系)

// ✅ V2 路径:@ComponentV2 V2 脚饰器体系(API 23+ 新项目用) @ComponentV2 struct V2Comp { @Param count: number = 0 // ✅ V2 @Param 本地状态(替代 V1 @State) @Provider('theme') theme: string = '#007DFF' // ✅ V2 @Provider 跨层级传递(替代 V1 @Provide) @Computed get doubled(): number { return this.count * 2 } // ✅ V2 @Computed 计算属性(替代 V1 @Watch) build() { Column() { Text(`V2 count=${this.count} doubled=${this.doubled}`).fontColor(this.theme) Button('改 count++').onClick(() => { this.count++ }) } } } // V2 路径新项目:@ComponentV2 V2 装饰器体系(@Param/@Once/@Event/@Provider/@Consumer/@Computed 全套)

为哈能跑:V2 路径新项目——@ComponentV2 V2 装饰器体系全套(@Param/@Once/@Event/@Provider/@Consumer/@Computed),API 23+ 才兼容,新项目用 V2。要写「状态管理新项目(API 23+ 等)」时用这个——V2 装饰器体系全套,新项目用 V2(@Computed 计算属性替代 @Watch 副作用回调更声明式,@Event 事件回调替代 @Link 双向绑定更单向数据流)。

场景 3:V1/V2 不混用边界(同一 struct 只用一套,混用编译报错)

// ❌ V1/V2 混用编译报错:同一 struct 只用一套装饰器 @Component struct BadMixV1Comp { @State count: number = 0 // ✅ V1 @State @Param title: string = '' // ❌ 编译报错(@Component 只能用 V1,@Param 是 V2) build() {} } @ComponentV2 struct BadMixV2Comp { @Param count: number = 0 // ✅ V2 @Param @State title: string = '' // ❌ 编译报错(@ComponentV2 只能用 V2,@State 是 V1) build() {} } // ✅ V1/V2 不混用边界:同一 struct 只用一套装饰器(@Component 用 V1,@ComponentV2 用 V2) @Component struct PureV1Comp { @State count: number = 0; @Prop title: string = ''; build() {} } @ComponentV2 struct PureV2Comp { @Param count: number = 0; @Param({ once }) title: string = ''; build() {} } // V1/V2 不混用边界:同一 struct 只用一套装饰器(混用编译报错,@Component 用 V1 或 @ComponentV2 用 V2)

为哈能跑:V1/V2 不混用边界——同一 struct 只用一套装饰器(@Component 用 V1,@ComponentV2 用 V2),混用编译报错。要写「V1/V2 不混用(同一 struct 只用一套等)」时用这个——同一 struct 只用一套装饰器,@Component 用 V1 或 @ComponentV2 用 V2,混用编译报错。

六、一句话哲学

状态管理装饰器不是单轨,是 V1/V2 双轨装饰器体系的同名功能不同名。ArkUI 的状态管理是 V1/V2 双轨装饰器体系——@Component V1 装饰器体系(@State/@Prop/@Link/@Provide/@Consume/@Watch/@Observed/@ObjectLink/AppStorage/LocalStorage)+ @ComponentV2 V2 装饰器体系(@Param/@Once/@Event/@Provider/@Consumer/@Computed)。根因不是单轨是双轨装饰器体系——V1 装饰器体系(@Component 装的 struct 只能用 V1 装饰器)+ V2 装饰器体系(@ComponentV2 装的 struct 只能用 V2 装饰器)+ V1 vs V2 装饰器映射边界(同名功能不同名:@State→@Param, @Prop→@Once, @Link→@Event, @Provide→@Provider, @Consume→@Consumer, @Watch→@Computed)+ V1/V2 不兼容边界(@Component 不能用 V2 装饰器,@ComponentV2 不能用 V1 装饰器,混用编译报错)。对比 React 状态管理 hooks 魔法(useState/useEffect/useMemo 钩子单轨),ArkTS 状态管理 V1/V2 双轨装饰器体系同名功能不同名。

状态联动深水区串讲:@Watch 绑 @State 变触发副作用回调槽(篇 68)+ @Link $ 语法双向绑定跨组件同步(篇 69)+ @Provide/@Consume 跨层级传递槽祖辈后辈同步(篇 70)+ @Observed/@ObjectLink 嵌套对象观测槽子属性改刷 UI(篇 71)+ AppStorage 应用级状态存储槽所有页面共享(篇 72)+ LocalStorage 页面级状态存储槽同页面多组件共享(篇 73)+ @StorageLink/@StorageProp 应用级状态绑定边界双向 vs 只读(篇 74)+ @LocalStorageLink/@LocalStorageProp 页面级状态绑定边界双向 vs 只读(篇 75)+ 状态管理装饰器全景边界 V1/V2 双轨(篇 76,@Component V1 体系 vs @ComponentV2 V2 体系同名功能不同名)——续「ArkUI 状态联动」深水区,讲状态联动深水区(@Watch 协作用回调槽边界 + @Link $ 语法双向绑定边界 + @Provide/@Consume 跨层级传递槽边界 + @Observed/@ObjectLink 嵌套对象观测槽边界 + AppStorage 应用级状态存储槽边界 + LocalStorage 页面级状态存储槽边界 + @StorageLink/@StorageProp 应用级状态绑定边界 + @LocalStorageLink/@LocalStorageProp 页面级状态绑定边界 + 状态管理装饰器全景边界 V1/V2 双轨)。

系列预告:下篇(篇 77)讲 PersistentStorage 持久化状态边界(跨启动状态根因),续「ArkUI 状态联动」深水区。五阶段哲学体系:类型哲学(50-52)→ 作用域哲学(53-55)→ 状态哲学(56-59)→ 渎染哲学(60-62)→ 组件设计(63-67)→ 状态联动深水区(68+)讲清 ArkTS/ArkUI 进阶哲学。

能力系列回链

能力系列篇本文进阶点
篇 27 V1/V2 装饰器用法状态管理装饰器全景边界根因(V1/V2 双轨同名功能不同名)
篇 13 @State 基础用法状态哲学:@State 赋值就刷 UI 依赖追踪
篇 59 @Watch 用法状态联动深水区:@Watch 副作用回调槽 vs onClick 直接调副作用边界

V1/V2 装饰器全景对照速查

┌─────────────┬──────────────────────┬─────────────────────────────────┐ │ V1(@Component)│ V2(@ComponentV2) │ 功能边界 │ ├─────────────┼──────────────────────┼─────────────────────────────────┤ │ @State │ @Param │ 本地状态:赋值刷 UI 依赖追踪 │ │ @Prop │ @Param({ once })/@Once│ 单向只读:父改子同步子不改 │ │ @Link │ @Event │ 双向绑定/事件回调:父子同步 │ │ @Provide │ @Provider │ 跨层级传递:祖辈定义后辈消费 │ │ @Consume │ @Consumer │ 跨层级消费:后辈消费祖辈状态 │ │ @Watch │ @Computed │ 副作用/计算属性:变触发回调 │ │ @Observed │ @Observed(V2 兼容) │ 标记类可观测:嵌套对象子属性改 │ │ @ObjectLink │ @Param(V2 接收) │ 嵌套对象引用:子属性改刷子组件 │ │ AppStorage │ @Param(V2 不用) │ 应用级状态:所有页面共享 │ │ LocalStorage │ @Param(V2 不用) │ 页面级状态:同页面多组件共享 │ └─────────────┴──────────────────────┴─────────────────────────────────┘

写鸿蒙 ArkUI 记住:状态管理装饰器不是单轨是 V1/V2 双轨装饰器体系的同名功能不同名——V1 脚饰器体系(@Component 装的 struct 只能用 V1 装饰器:@State/@Prop/@Link/@Provide/@Consume/@Watch/@Observed/@ObjectLink/AppStorage/LocalStorage)+ V2 装饰器体系(@ComponentV2 装的 struct 只能用 V2 装饰器:@Param/@Once/@Event/@Provider/@Consumer/@Computed)+ V1 vs V2 脚饰器映射边界(同名功能不同名:@State→@Param, @Prop→@Once, @Link→@Event, @Provide→@Provider, @Consume→@Consumer, @Watch→@Computed)+ V1/V2 不兼容边界(@Component 不能用 V2 装饰器,@ComponentV2 不能用 V1 装饰器,混用编译报错)。V1 路径首选用 @Component V1 装饰器体系全套(90% 场景,API 21+ 全兼容),V2 路径新项目用 @ComponentV2 V2 装饰器体系全套(API 23+ 新项目,@Computed 计算属性替代 @Watch 副作用回调更声明式,@Event 事件回调替代 @Link 双向绑定更单向数据流),V1/V2 不混用边界同一 struct 只用一套装饰器(混用编译报错)。V1/V2 双轨同名功能不同名是 ArkUI 状态联动深水区核心!