鸿蒙开发实战:Component V1 与 V2 的核心差异及迁移指南
参考文档: https://developer.huawei.com/consumer/cn/doc/architecture-guides/common-v1_26-ts_88-0000002312256121
在鸿蒙应用开发中,Component(组件)是构建 UI 的基础单元。随着 ArkUI 框架的迭代,Component 经历了从 V1 到 V2 的演进,两者在设计理念、使用方式和功能支持上存在显著差异。很多开发者在项目中会纠结:新项目该用 V1 还是 V2?老项目是否需要迁移到 V2?本文结合实际开发经验,从核心定位、关键差异、代码实现到迁移建议,全方位解析二者的区别。
一、状态管理概述
在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时状态作为参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化导致的UI重新渲染,在ArkUI中统称为状态管理机制。

- View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。
- State:状态,指驱动UI更新的数据。通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。
二、状态管理V1
1. @State装饰器
被状态变量装饰器装饰的变量称为状态变量,使普通变量具备状态属性。当状态变量改变时,会触发其直接绑定的UI组件渲染更新。
- @State装饰的变量生命周期与其所属自定义组件的生命周期相同。
// 简单类型
@State count: number = 0;
// 可以观察到值的变化
this.count = 1;
2. @Prop装饰器
@Prop装饰的变量可以和父组件建立单向同步关系。
- @Prop装饰的变量允许本地修改,但修改不会同步回父组件。
- 当数据源更改时,@Prop装饰的变量都会更新,并且会覆盖本地所有更改。
@Component
struct Son {
@Prop message: string = 'Hi';
build() {
Column() {
Text(this.message)
}
}
}
@Entry
@Component
struct Father {
@State message: string = 'Hello';
build() {
Column() {
Text(this.message)
Button(`father click`).onClick(() => {
this.message += '*';
})
Son({ message: this.message })
}
}
}
3. @Link装饰器
@Link装饰的变量与其父组件中的数据源共享相同的值。
class Info {
info: string = 'Hello';
}
@Component
struct Child {
// 正确写法
@Link test: Info;
build() {
Text(this.test.info)
}
}
@Entry
@Component
struct LinkExample {
@State info: Info = new Info();
build() {
Column() {
// 正确写法
Child({test: this.info})
}
}
}
4. @Provide装饰器和@Consume装饰器
应用于与后代组件的双向数据同步、状态数据在多个层级之间传递的场景。
- @Provide装饰的状态变量自动对其所有后代组件可用,开发者不需要多次在组件之间传递变量。
- 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以更便捷的在多层级父子组件之间传递。
- @Provide和@Consume通过变量名或者变量别名绑定,需要类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
@Component
struct Child {
@Consume num: number;
// 从API version 20开始,@Consume装饰的变量支持设置默认值
@Consume num1: number = 17;
build() {
Column() {
Text(`num的值: ${this.num}`)
Text(`num1的值:${this.num1}`)
}
}
}
@Entry
@Component
struct Parent {
@Provide num: number = 10;
build() {
Column() {
Text(`num的值: ${this.num}`)
Child()
}
}
}
5. @Observed装饰器和@ObjectLink装饰器
- @State、@Prop、@Link、@Provide和@Consume装饰器,仅能观察到第一层的变化
- @ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步
- 使用new创建被@Observed装饰的类,可以被观察到属性的变化。
- 组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
- @Observed用于嵌套类场景中,观察对象类属性变化,要配合自定义组件使用(示例详见嵌套对象),如果要做数据双/单向同步,需要搭配@ObjectLink或者@Prop使用(示例详见@Prop与@ObjectLink的差异)。
@Observed
class DateClass extends Date {
constructor(args: number | string) {
super(args);
}
}
@Observed
class NewDate {
public data: DateClass;
constructor(data: DateClass) {
this.data = data;
}
}
@Component
struct Child {
label: string = 'date';
@ObjectLink data: DateClass;
build() {
Column() {
Button(`child increase the day by 1`)
.onClick(() => {
this.data.setDate(this.data.getDate() + 1);
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.data
})
}
}
}
@Entry
@Component
struct Parent {
@State newData: NewDate = new NewDate(new DateClass('2023-1-1'));
build() {
Column() {
Child({ label: 'date', data: this.newData.data })
Button(`parent update the new date`)
.onClick(() => {
this.newData.data = new DateClass('2023-07-07');
})
Button(`ViewB: this.newData = new NewDate(new DateClass('2023-08-20'))`)
.onClick(() => {
this.newData = new NewDate(new DateClass('2023-08-20'));
})
}
}
}
6. @Watch装饰器
@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。
@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当严格相等判断的结果是false(即不相等)的情况下,就会触发@Watch的回调。
@Component
struct TotalView {
@Prop @Watch('onCountUpdated') count: number = 0;
@State total: number = 0;
// @Watch 回调
onCountUpdated(propName: string): void {
this.total += this.count;
}
build() {
Text(`Total: ${this.total}`)
}
}
@Entry
@Component
struct CountModifier {
@State count: number = 0;
build() {
Column() {
Button('add to basket')
.onClick(() => {
this.count++
})
TotalView({ count: this.count })
}
}
}
7. @Track装饰器
@Track是class对象的属性装饰器。当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新;如果class类中使用了@Track装饰器,则未被@Track装饰器装饰的属性不能在UI中使用,如果使用,会发生运行时报错。
class LogTrack {
@Track str1: string;
@Track str2: string;
constructor(str1: string) {
this.str1 = str1;
this.str2 = 'World';
}
}
class LogNotTrack {
str1: string;
str2: string;
constructor(str1: string) {
this.str1 = str1;
this.str2 = '世界';
}
}
@Entry
@Component
struct AddLog {
@State logTrack: LogTrack = new LogTrack('Hello');
@State logNotTrack: LogNotTrack = new LogNotTrack('你好');
isRender(index: number) {
console.log(`Text ${index} is rendered`);
return 50;
}
build() {
Row() {
Column() {
Text(this.logTrack.str1) // Text1
.fontSize(this.isRender(1))
.fontWeight(FontWeight.Bold)
Text(this.logTrack.str2) // Text2
.fontSize(this.isRender(2))
.fontWeight(FontWeight.Bold)
Button('change logTrack.str1')
.onClick(() => {
this.logTrack.str1 = 'Bye';
})
Text(this.logNotTrack.str1) // Text3
.fontSize(this.isRender(3))
.fontWeight(FontWeight.Bold)
Text(this.logNotTrack.str2) // Text4
.fontSize(this.isRender(4))
.fontWeight(FontWeight.Bold)
Button('change logNotTrack.str1')
.onClick(() => {
this.logNotTrack.str1 = '再见';
})
}
.width('100%')
}
.height('100%')
}
}
三、状态管理V2
1. @Local装饰器
@Local表示组件内部的状态,使得自定义组件内部的变量具有观测变化的能力
-
被@Local装饰的变量无法从外部初始化,因此必须在组件内部进行初始化。
-
当被@Local装饰的变量变化时,会刷新使用该变量的组件。
-
@Local支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型。
-
@Local的观测能力仅限于被装饰的变量本身。当装饰简单类型时,能够观测到对变量的赋值;当装饰对象类型时,仅能观测到对对象整体的赋值;当装饰数组类型时,能观测到数组整体以及数组元素项的变化;当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化。详见观察变化。
-
@Local支持null、undefined以及联合类型。
@Entry @ComponentV2 struct Index { @Local count: number = 0; @Local message: string = 'Hello'; @Local flag: boolean = false; build() { Column() { Text(`${this.count}`) Text(`${this.message}`) Text(`${this.flag}`) Button('change Local') .onClick(()=>{ // 当@Local装饰简单类型时,能够观测到对变量的赋值 this.count++; this.message += ' World'; this.flag = !this.flag; }) } } }
2. @Param装饰器
@Param表示组件从外部传入的状态,使得父子组件之间的数据能够进行同步
- @Param装饰的变量支持本地初始化,但不允许在组件内部直接修改。
- 被@Param装饰的变量能够在初始化自定义组件时从外部传入,当数据源也是状态变量时,数据源的修改会同步给@Param。
- @Param可以接受任意类型的数据源,包括普通变量、状态变量、常量、函数返回值等。
- @Param装饰的变量变化时,会刷新该变量关联的组件。
- @Param支持对基本类型(如number、boolean、string、Object、class)、内嵌类型(如Array、Set、Map、Date),以及null、undefined和联合类型进行观测。
- 对于复杂类型如类对象,@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源。
- @Param的观测能力仅限于被装饰的变量本身。
@Entry
@ComponentV2
struct Index {
@Local count: number = 0;
@Local message: string = 'Hello';
@Local flag: boolean = false;
build() {
Column() {
Text(`Local ${this.count}`)
Text(`Local ${this.message}`)
Text(`Local ${this.flag}`)
Button('change Local')
.onClick(()=>{
// 对数据源的更改会同步给子组件
this.count++;
this.message += ' World';
this.flag = !this.flag;
})
Child({
count: this.count,
message: this.message,
flag: this.flag
})
}
}
}
@ComponentV2
struct Child {
@Require @Param count: number;
@Require @Param message: string;
@Require @Param flag: boolean;
build() {
Column() {
Text(`Param ${this.count}`)
Text(`Param ${this.message}`)
Text(`Param ${this.flag}`)
}
}
}
3. @Once装饰器
@Once装饰器在变量初始化时接受外部传入值进行初始化,后续数据源更改不会同步给子组件
- @Once必须搭配@Param使用,单独使用或搭配其他装饰器使用都是不允许的。
- @Once不影响@Param的观测能力,仅针对数据源的变化做拦截。
- @Once与@Param装饰变量的先后顺序不影响使用功能。
- @Once与@Param搭配使用时,可以在本地修改@Param变量的值。
@ComponentV2
struct ChildComponent {
@Param @Once onceParam: string = '';
build() {
Column() {
Text(`onceParam: ${this.onceParam}`)
}
}
}
@Entry
@ComponentV2
struct MyComponent {
@Local message: string = 'Hello World';
build() {
Column() {
Text(`Parent message: ${this.message}`)
Button('change message')
.onClick(() => {
this.message = 'Hello Tomorrow';
})
ChildComponent({ onceParam: this.message })
}
}
}
4. @Event装饰器
由于@Param装饰的变量在本地无法更改,使用@Event装饰器装饰回调方法并调用,可以实现更新数据源的变量,再通过@Local的同步机制,将修改同步回@Param,以此达到主动更新@Param装饰变量的效果。
@Entry
@ComponentV2
struct Index {
@Local title: string = 'Title One';
@Local fontColor: Color = Color.Red;
build() {
Column() {
Child({
title: this.title,
fontColor: this.fontColor,
changeFactory: (type: number) => {
if (type == 1) {
this.title = 'Title One';
this.fontColor = Color.Red;
} else if (type == 2) {
this.title = 'Title Two';
this.fontColor = Color.Green;
}
}
})
}
}
}
@ComponentV2
struct Child {
@Param title: string = '';
@Param fontColor: Color = Color.Black;
@Event changeFactory: (x: number) => void = (x: number) => {};
build() {
Column() {
Text(`${this.title}`)
.fontColor(this.fontColor)
Button('change to Title Two')
.onClick(() => {
this.changeFactory(2);
})
Button('change to Title One')
.onClick(() => {
this.changeFactory(1);
})
}
}
}
5. @Monitor装饰器
@Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力
@Entry
@ComponentV2
struct Index {
@Local message: string = 'Hello World';
@Local name: string = 'Tom';
@Local age: number = 24;
@Monitor('message', 'name')
onStrChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.info(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
});
}
build() {
Column() {
Button('change string')
.onClick(() => {
this.message += '!';
this.name = 'Jack';
})
}
}
}
6.@ObservedV2装饰器和@Trace装饰器
@ObservedV2装饰器与@Trace装饰器用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力
@ObservedV2
class Son {
@Trace age: number = 100;
}
class Father {
son: Son = new Son();
}
@Entry
@ComponentV2
struct Index {
father: Father = new Father();
build() {
Column() {
// 当点击改变age时,Text组件会刷新
Text(`${this.father.son.age}`)
.onClick(() => {
this.father.son.age++;
})
}
}
}
###7.@Provider装饰器和@Consumer装饰器
@Provider,即数据提供方,其所有的子组件都可以通过@Consumer绑定相同的key来获取@Provider提供的数据。
@Consumer,即数据消费方,可以通过绑定同样的key获取其最近父节点的@Provider的数据,当查找不到@Provider的数据时,使用本地默认值
@Entry
@ComponentV2
struct Parent {
@Provider() str: string = 'hello';
build() {
Column() {
Button(this.str)
.onClick(() => {
this.str += '0';
})
Child()
}
}
}
@ComponentV2
struct Child {
// @Consumer装饰的属性str和Parent组件中@Provider装饰的属性str名称相同,因此建立了双向绑定关系
@Consumer() str: string = 'world';
build() {
Column() {
Button(this.str)
.onClick(() => {
this.str += '0';
})
}
}
}
###8.@Computed装饰器
@Computed为方法装饰器,装饰getter方法。@Computed会检测被计算的属性变化,当被计算的属性变化时,@Computed只会被求解一次。
@ComponentV2
struct Child {
@Param double: number = 100;
@Event $double: (val: number) => void;
build() {
Button('ChildChange')
.onClick(() => {
this.$double(200);
})
}
}
@Entry
@ComponentV2
struct Index {
@Local count: number = 100;
@Computed
get double() {
return this.count * 2;
}
// @Computed装饰的属性是只读的,开发者自己实现的setter不生效,且产生编译时报错
set double(newValue : number) {
this.count = newValue / 2;
}
build() {
Scroll() {
Column({ space: 3 }) {
Text(`${this.count}`)
// 错误写法,@Computed装饰的属性是只读的,无法与双向绑定连用。
Child({ double: this.double!! })
}
}
}
}
9. @Type装饰器
@Type标记类属性,使得类属性序列化时不丢失类型信息,便于类的反序列化。
class Sample {
data: number = 0;
}
@ObservedV2
class Info {
@Type(Sample)
@Trace sample: Sample = new Sample(); // 正确用法
}
@Observed
class Info2 {
@Type(Sample)
sample: Sample = new Sample(); // 错误用法,不能用在@Observed装饰的类中,编译时报错
}
@ComponentV2
struct Index {
@Type(Sample)
sample: Sample = new Sample(); // 错误用法,不能用在自定义组件中
build() {
}
}
###10.@ReusableV2装饰器
@ReusableV2用于装饰V2的自定义组件,表明该自定义组件具有被复用的能力
@ReusableV2 // 装饰ComponentV2的自定义组件
@ComponentV2
struct ReusableV2Component {
@Local message: string = 'Hello World';
build () {
Column() {
Text(this.message)
}
}
}
四、V1和V2能力对比
| V1装饰器名\场景 | V2装饰器名\API | 说明 |
|---|---|---|
| @Observed | @ObservedV2 | 表明当前对象为可观察对象。但两者能力并不相同。@Observed可观察第一层的属性,需要搭配@ObjectLink使用才能生效。@ObservedV2本身无观察能力,仅代表当前class可被观察,如果要观察其属性,需要搭配@Trace使用。详情见@ObjectLink/@Observed/@Track迁移场景。 |
| @Track | @Trace | V1装饰器@Track为精确观察,不使用则无法做到类属性的精准观察。V2@Trace装饰的属性可以被精确跟踪观察。详情见@ObjectLink/@Observed/@Track迁移场景。 |
| @Component | @ComponentV2 | @Component为搭配V1状态变量使用的自定义组件装饰器。@ComponentV2为搭配V2状态变量使用的自定义组件装饰器。 |
| @State | 无外部初始化:@Local外部初始化一次:@Param@Once | @State和@Local类似都是数据源的概念,在不需要外部传入初始化时,可直接迁移。如果需要外部传入初始化,则可以迁移为@Param@Once。详情见@State迁移场景。 |
| @Prop | @Param | @Prop和@Param类似都是自定义组件参数的概念。当输入参数为复杂类型时,@Prop为深拷贝,@Param为引用。详情见@Prop迁移场景。 |
| @Link | @Param@Event | @Link是框架自己封装实现的双向同步,对于V2开发者可以通过@Param@Event自己实现双向同步。详情见@Link迁移场景。 |
| @ObjectLink | @ObservedV2@Trace | 直接兼容,@ObjectLink需要被@Observed装饰的class的实例初始化,主要应用于观察嵌套类场景。在状态管理V2中可以使用@ObservedV2@Trace。详情见@ObjectLink/@Observed/@Track迁移场景。 |
| @Provide | @Provider | 兼容。详情见@Provide@Consume迁移场景。 |
| @Consume | @Consumer | 兼容。详情见@Provide@Consume迁移场景。 |
| @Watch | @Monitor | @Watch用于监听V1状态变量的变化,具有监听状态变量本身和其第一层属性变化的能力。状态变量可观察到的变化会触发其@Watch监听事件。@Monitor用于监听V2状态变量的变化,搭配@Trace使用,可有深层监听的能力。状态变量在一次事件中多次变化时,仅会以最终的结果判断是否触发@Monitor监听事件。详情见@Watch迁移场景。 |
| 重复计算 | @Computed | 状态管理V1无计算属性相关能力,状态管理V2可使用@Computed避免重复计算。详情见@Computed使用场景。 |
| LocalStorage | @ObservedV2@Trace | 兼容。详情见LocalStorage迁移场景。 |
| AppStorage | AppStorageV2 | 兼容。详情见AppStorage迁移场景。 |
| Environment | 调用Ability接口获取系统环境变量 | Environment获取环境变量能力和AppStorage耦合。在V2中可直接调用Ability接口获取系统环境变量。详情见Environment迁移场景。 |
| PersistentStorage | PersistenceV2 | PersistentStorage持久化能力和AppStorage耦合,PersistenceV2持久化能力可独立使用。详情见PersistentStorage迁移场景。 |
五、V1V2混用使用
优先选 V2 的场景
-
新项目开发:直接采用 V2,享受更简洁的语法和更全的新特性支持
-
跨设备适配项目:V2 对手机、平板、智慧屏的适配更友好
-
复杂 UI 交互项目:V2 的组合式 API 和状态管理更适合处理复杂逻辑
-
追求开发效率的项目:V2 的代码量更少,上手后开发速度更快
继续用 V1 的场景
-
维护老项目:若项目已基于 V1 开发完成,无需强制迁移
-
团队技术栈适配:团队成员多为安卓 /iOS 转过来,对类继承模式更熟悉
-
简单 UI 组件开发:如纯展示类组件,V1 的写法更直观
混合使用的场景
- 老项目新功能开发:新功能用 V2 实现,通过 “桥接组件” 与 V1 代码兼容
- 组件库开发:核心组件用 V2 实现,提供 V1 的适配层供老项目使用
857

被折叠的 条评论
为什么被折叠?



