四时宝库

程序员的知识宝库

一文搞清楚HarmonyOS中父子组件数据传递装饰器的奇妙之旅

程序员Feri一名12年+的程序员,做过开发带过团队创过业,擅长Java、嵌入式、鸿蒙、人工智能等,专注于程序员成长那点儿事,希望在成长的路上有你相伴!君志所向,一往无前!


状态管理 V1 版本:父子组件数据传递装饰器的奇妙之旅

在HarmonyOS NEXT开发的奇妙世界里,组件就像是一个个独立的小王国,它们各自有着自己的职责和功能。但是,这些小王国之间并不是孤立存在的,它们需要相互交流、相互协作,才能构建出一个完整而强大的应用帝国。

而在这个过程中,数据传递就像是王国之间的信使,负责在不同的组件之间传递信息。

在状态管理的 V1 版本中,我们有两位强大的信使:@Prop 和 @Link。它们各自有着独特的能力,能够帮助我们实现父子组件之间的数据传递。接下来,让我们一起踏上这场奇妙的旅程,探索它们的奥秘吧!

1. @Prop:单向传递的快递员

1.1 基本概念

想象一下,有一个快递员叫 @Prop,他负责从一个王国(父组件)向另一个王国(子组件)传递包裹(数据)。

这个快递员有一个特殊的规则:他只负责把包裹从父组件送到子组件,而子组件对包裹所做的任何修改,他都不会带回给父组件。

1.2 装饰器参数

@Prop 就像是一个不需要任何证件的快递员,他不需要任何参数就能工作。

1.3 同步类型

单向同步。这就好比父组件是包裹的发送者,子组件是包裹的接收者。

发送者可以随时发送新的包裹,接收者会收到最新的包裹内容。但是,接收者对包裹所做的任何修改,发送者都不会知道。

1.4 允许装饰的变量类型

@Prop 这个快递员非常能干,几乎什么类型的包裹都能送。

他可以送 Object、class、string、number、boolean、enum 类型的包裹,甚至还可以送这些类型的数组包裹。

1.5 被装饰变量的初始值

@Prop 允许子组件在收到包裹之前,先给自己准备一个初始的包裹内容。但是,一旦收到父组件的包裹,这个初始内容就会被父组件的包裹内容覆盖。

1.6 示例代码

下面是一个有趣的示例,展示了 @Prop 的使用:

// 子组件:魔法按钮
@Component
export struct MagicButton {
  // 魔法按钮的图标库
  magicIcons: Resource[] = [
    $r("sys.media.star"),
    $r("sys.media.moon"),
    $r("sys.media.sun")
  ];
  
  // 按钮的当前状态
  @State currentState: number = 0;
  
  // 按钮的名称,由父组件传递
  @Prop buttonName: string = "普通按钮";

  build() {
    Column() {
      // 显示当前的魔法图标
      Image(this.magicIcons[this.currentState])
        .width(100)
        .height(100)
        .animation({ duration: 500, curve: "ease-in-out" });
      
      // 魔法按钮
      Button(this.buttonName)
        .onClick(() => {
          // 切换魔法状态
          this.currentState = (this.currentState + 1) % 3;
          
          // 尝试修改按钮名称,但这不会影响父组件
          this.buttonName = `魔法状态${this.currentState}`;
          console.log(`子组件将按钮名称改为:${this.buttonName}`);
        })
        .width(150)
        .height(50)
        .backgroundColor("#6200EA")
        .fontColor(Color.White)
        .borderRadius(25);
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
}

// 父组件:魔法控制中心
@Entry
@Component
struct MagicControlCenter {
  // 控制中心的状态
  @State magicWord: string = "魔法";
  
  build() {
    Column() {
      // 输入魔法词
      TextInput({ placeholder: "输入魔法词..." })
        .onChange(v => {
          this.magicWord = v;
          console.log(`父组件设置魔法词为:${this.magicWord}`);
        })
        .width("80%")
        .margin({ top: 50 })
        .backgroundColor("#E0E0E0")
        .borderRadius(10)
        .padding(10);
      
      // 显示当前魔法词
      Text(`当前魔法词:${this.magicWord}`)
        .fontSize(18)
        .margin({ top: 20 });
      
      // 使用魔法按钮
      MagicButton({ buttonName: this.magicWord })
        .margin({ top: 50 });
    }
    .width("100%")
    .height("100%")
    .padding(20);
  }
}


1.7 代码解释

在这个魔法世界的示例中,我们有一个魔法控制中心(父组件)和一个魔法按钮(子组件)。控制中心可以通过输入框设置魔法词,然后将这个魔法词作为 buttonName 属性传递给魔法按钮。

当我们在控制中心输入新的魔法词时,魔法按钮会立即更新显示新的名称。但是,当我们点击魔法按钮时,虽然按钮内部会尝试修改 buttonName,但这个修改不会影响到控制中心的 magicWord。这就好比魔法按钮只是在自己的小世界里改变了按钮名称的副本,而真正的魔法词仍然掌握在控制中心手中。

2. @Link:心灵感应的双胞胎

2.1 基本概念

现在,让我们认识另一位神奇的信使 @Link。他就像是一对心灵感应的双胞胎,无论相隔多远,其中一个的想法和感受都会立即被另一个感知到。

在组件的世界里,@Link 可以让父组件和子组件共享同一个数据,任何一方对数据的修改都会立即反映到另一方。

2.2 装饰器参数

@Link 就像一对不需要任何介绍的双胞胎,他们之间的心灵感应是天生的,不需要任何参数来建立联系。

2.3 同步类型

双向同步。这意味着父组件和子组件就像双胞胎一样,任何一方对共享数据的修改都会立即被另一方感知到。

2.4 允许装饰的变量类型

@Link 这对双胞胎非常包容,几乎可以共享任何类型的数据。

他们可以共享 Object、class、string、number、boolean、enum 类型的数据,甚至还可以共享这些类型的数组和 Date 类型的数据。

2.5 被装饰变量的初始值

@Link 这对双胞胎必须从同一个源头获得他们的初始数据,因此子组件不能自己初始化 @Link 装饰的变量,必须由父组件提供初始值。

2.6 示例代码

下面是一个有趣的示例,展示了 @Link 的使用:

doubaocanvas

// 子组件:能量水晶
@Component
export struct EnergyCrystal {
  // 水晶的不同形态
  crystalForms: Resource[] = [
    $r("sys.media.crystal_blue"),
    $r("sys.media.crystal_green"),
    $r("sys.media.crystal_purple")
  ];
  
  // 水晶的当前状态
  @State currentForm: number = 0;
  
  // 能量值,与父组件双向绑定
  @Link energyLevel: number;
  
  // 上次点击时间,用于防止双击
  @State lastClickTime: number = 0;

  build() {
    Column() {
      // 显示当前水晶形态
      Image(this.crystalForms[this.currentForm])
        .width(150)
        .height(150)
        .animation({ duration: 500, curve: "spring" });
      
      // 显示当前能量值
      Text(`能量值:${this.energyLevel}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })
        .fontColor(this.getEnergyColor());
      
      // 点击水晶增加能量
      Button("点击水晶增加能量")
      .onClick(() => {
        const now = Date.now();
        // 防止双击
        if (now - this.lastClickTime > 300) {
          this.lastClickTime = now;
          
          // 增加能量值
          this.energyLevel += 10;
          
          // 根据能量值改变水晶形态
          this.currentForm = Math.floor(this.energyLevel / 100) % 3;
          
          // 播放能量增加动画
          this.playEnergyAnimation();
        }
      });
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
  
  // 根据能量值获取颜色
  getEnergyColor() {
    if (this.energyLevel < 50) return Color.Red;
    if (this.energyLevel < 100) return Color.Orange;
    return Color.Green;
  }
  
  // 播放能量增加动画
  playEnergyAnimation() {
    // 这里可以添加更复杂的动画效果
    console.log("能量增加动画播放");
  }
}

// 父组件:能量监控站
@Entry
@Component
struct EnergyMonitoringStation {
  // 总能量值
  @State totalEnergy: number = 0;
  
  // 能量收集器数量
  @State collectorCount: number = 1;
  
  build() {
    Column() {
      // 显示总能量
      Text(`总能量:${this.totalEnergy}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 30 });
      
      // 能量收集器控制
      Row() {
        Text("能量收集器数量:")
          .fontSize(18);
        
        Button("-")
          .onClick(() => {
            if (this.collectorCount > 1) {
              this.collectorCount--;
            }
          })
          .width(40)
          .height(40)
          .backgroundColor("#B71C1C")
          .fontColor(Color.White)
          .margin({ left: 20 });
        
        Text(`${this.collectorCount}`)
          .fontSize(18)
          .margin({ left: 10, right: 10 });
        
        Button("+")
          .onClick(() => {
            if (this.collectorCount < 3) {
              this.collectorCount++;
            }
          })
          .width(40)
          .height(40)
          .backgroundColor("#2E7D32")
          .fontColor(Color.White);
      }
      .margin({ top: 20 });
      
      // 显示能量收集器
      ForEach(Array(this.collectorCount).fill(0), (_, index) => {
        EnergyCrystal({ energyLevel: this.totalEnergy })
          .margin({ top: 30 });
      });
      
      // 全局能量控制
      Button("重置能量")
        .onClick(() => {
          this.totalEnergy = 0;
        })
        .width("80%")
        .height(50)
        .backgroundColor("#1A237E")
        .fontColor(Color.White)
        .margin({ top: 50 })
        .borderRadius(25);
    }
    .width("100%")
    .height("100%")
    .padding(20);
  }
}

2.7 代码解释

在这个能量世界的示例中,我们有一个能量监控站(父组件)和一个或多个能量水晶(子组件)。

监控站负责监控总能量值,而能量水晶则负责收集能量。

当我们点击能量水晶时,水晶的能量值会增加。由于能量值是通过 @Link 与父组件双向绑定的,所以父组件的总能量值也会立即增加。

同样地,当我们在父组件中重置能量值时,所有子组件的能量值也会立即更新。

这就好比能量监控站和能量水晶是一对心灵感应的双胞胎,无论哪一方的能量发生变化,另一方都能立即感知到并做出相应的反应。

3. 浅拷贝和深拷贝的奇妙比喻

在我们的数据传递旅程中,还有两个重要的概念需要理解:浅拷贝和深拷贝。

让我们用一个有趣的比喻来解释它们。

假设你有一本非常珍贵的魔法书,里面有许多强大的咒语。浅拷贝就像是给这本书拍了一张照片,照片上的咒语看起来和原书一模一样,但实际上只是一个图像,如果你试图在照片上修改咒语,是不会有任何效果的。

而深拷贝则像是完全复制了一本魔法书,包括书中的每一个字、每一个魔法符号。这两本书是完全独立的,你可以在其中一本上随意修改咒语,而不会影响另一本。

在 @Prop 和 @Link 的使用中,如果传递的是引用类型的数据(如对象或数组),就需要注意是使用浅拷贝还是深拷贝,以避免意外的数据修改。

4. 总结

在这场奇妙的旅程中,我们认识了两位强大的信使:@Prop 和 @Link。@Prop 就像是一个单向传递的快递员,负责将数据从父组件传递到子组件,但不会将子组件的修改带回给父组件。

而 @Link 则像是一对心灵感应的双胞胎,让父组件和子组件能够共享数据,任何一方的修改都会立即被另一方感知到。

通过这两位信使,我们可以在组件之间建立起高效、可靠的数据传递通道,让我们的应用帝国更加稳固和强大。

希望通过这个生动有趣的比喻,你对 @Prop 和 @Link 有了更深入的理解。在你的HarmonyOS NEXT开发旅程中,好好利用这两位信使,创造出更加精彩的应用吧!

好啦,本篇就到这里啦,加油哟!关注我,跟我一起成长哈!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接