• 作者:老汪软件技巧
  • 发表时间:2024-08-29 10:02
  • 浏览量:

一、 自定义组件基础

在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。

相比于轻量级 UI 复用机制 @Builder,自定义组件的功能更为强大,日常开发中如果要进行 UI 或业务逻辑进行复用,需要掌握自定义组件的能力。

1. 基本使用

语法:

// 定义
@Component
struct MyComponent {
  // 状态变量
  @State message:string =''
  build(){
    // .... 描述 UI
  }
}
//----------使用-----------
// 1.不传递参数使用
// MyComponent() 
// 2.传递参数使用:通过传递参数的方式 设置子组件中 messsage 的值
// MyComponent({message:'xxx'})

2. 成员函数/变量

自定义组件除了必须要实现build()函数外,还可以定义其他的成员函数,以及成员变量

// HelloComponent.ets
@Component
export struct HelloComponent {
  // 成员变量
  info: string = '感觉自己闷闷哒'
  // 成员变量也可以是函数
  sayHello=()=>{}
  // 状态变量
  @State message: string = 'Hello, World!';
  // 成员函数
  sayHi() {
    console.log('你好呀')
  }
  build() {
    // HelloComponent自定义组件组合系统组件Row和Text
    Column() {
      Text(this.message)
      Text(this.info)
      Button('修改数据')
        .onClick(() => {
          this.info = '(*  ̄3)(ε ̄ *)'
          this.message = 'Hello,ArkTS'
          this.sayHi()
          this.sayHello()
        })
    }
  }
}
// 页面的.ets
import { HelloComponent } from './components/HelloComponent'
@Entry
@Component
struct CustomComponentDemo {
  build() {
    Column() {
      // 使用组件内部定义的初始值
      HelloComponent()
      // 使用传入的值,覆盖子组件的默认值
      HelloComponent({ info: '你好', message: 'ArkTS' })
      // 函数也可以传入
      HelloComponent({ sayHello(){ console.log('传入的逻辑') } })
    }
  }
}

2.3. 通用样式事件

自定义组件可以通过点语法的形式设置通用样式,通用事件

@Component
struct MyComponent2 {
  build() {
    Button(`Hello World`)
  }
}
@Entry
@Component
struct MyComponent {
  build() {
    Row() {
      MyComponent2()
        .width(200)
        .height(300)
        .backgroundColor(Color.Red)
        .onClick(() => {
            console.log('外部添加的点击事件')
          })
    }
  }
}

二、 构建函数-@BuilderParam 传递 UI

就是自定义组件允许外部传递 UI

1. 单个@BuilderParam参数

@Component
struct MyButtom {
   // 第一步 定义自定义构建函数
  @Builder myBuilder(){
    Button('默认按钮')
  }
   //第二步 定义 BuilderParam 接受外部传入的 ui,并设置默认值
  @BuilderParam my:()=>void = this.myBuilder
  build() {
    Column({space:20}) {
      Text('================')
      //第三步使用 @BuilderParam 装饰的成员变量
      this.my()
      Text('================')
    }
    .width(200)
    .height(200)
    .padding({top:30})
    .border({width:10})
    .borderRadius(100)
  }
}
@Entry
@Component
struct Index {
  build() {
    Column({ space: 10 }) {
      MyButtom()
      MyButtom(){
        Button('外部组件')
      }
      MyButtom(){
        Image($r('app.media.avatar'))
          .width(50)
      }
    }
    .width('100%')
    .height('100%')
  }
}

2. 多个@BuilderParam 参数

子组件有多个BuilderParam,必须通过参数的方式来传入

@Component
struct MyButtom {
 // 第一步 定义自定义构建函数
  @Builder
  myBuilder() {
    Text('<')
  }
   //第二步   定义 BuilderParam 接受外部传入的 ui,并设置默认值
  @BuilderParam leftBui: () => void = this.myBuilder
  @BuilderParam centerBui: () => void = this.myBuilder
  @BuilderParam rightBui: () => void = this.myBuilder
  build() {
    Row() {
    //第三步使用 @BuilderParam 装饰的成员变量
      this.leftBui()
      this.centerBui()
      this.rightBui()
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .backgroundColor(Color.White)
  }
}
@Entry
@Component
struct Index {
  @Builder leftBuilder(){
    Text('返回')
      .fontSize(30)
  }
  @Builder centerBuilder(){
    Text('首页')
      .fontSize(30)
  }
  @Builder rightBuilder(){
    Text('菜单')
      .fontSize(30)
  }
  build() {
    Column({ space: 10 }) {
      //没有传参
      MyButtom()
     
    //第四步  通过参数的形式传入多个 Builder
      MyButtom({
        leftBui:this.leftBuilder,
        centerBui:this.centerBuilder,
        rightBui:this.rightBuilder
      })
    }
    .backgroundColor('#ccc')
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
  }
}

三、 状态共享@Prop -父子单向

组件的基本使用方式__定义组件的两种方式

@Prop 装饰的变量可以和父组件建立单向的同步关系。@Prop 装饰的变量是可变的,但是变化不会同步回其父组件。

1. 简单类型

简单类型 string、number、boolean、enum

注意:

修改父组件数据,会同步更新子组件修改子组件@Prop 修饰的数据,子组件 UI 更新,但会被父组件状态更新覆盖通过回调函数的方式修改父组件的数据,然后触发@Prop数据的更新

@Component
struct MyButtom {
  @Prop
  num:number = 0
  build() {
    Button(`子组件  ${this.num}`)
      .backgroundColor(Color.Black)
  }
}
@Entry
@Component
struct Index {
  @State num:number = 100
  build() {
    Column({space:10}) {
      Button(`父组件  ${this.num}`)
        .onClick(()=>{
          this.num++
        })
      MyButtom({ num: this.num })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

2. 复杂类型

复杂类型的做法类似,通过回调函数将需要修改的数据传递给父组件即可

@Component
struct MyButtom {
  @Prop
  num: number = 0
  addNum: (n: number) => void = () => {}  //接收父组件的函数
  build() {
    Button(`子组件加加  ${this.num}`)
      .backgroundColor(Color.Black)
      .onClick(() => {
        this.addNum(5)   //点击时使父组件的num+5,然后带动子组件的num增加
      })
  }
}
@Entry
@Component
struct Index {
  @State num: number = 100
  addNum=(n:number)=>{
    this.num+=n
  }
  build() {
    Column({ space: 10 }) {
      Button(`父组件减减  ${this.num}`)
        .onClick(() => {
          this.num--
        })
      MyButtom({ num: this.num, addNum: this.addNum }) //给子组件传一个带参数的箭头函数this.addNum
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

小案例

需求:

小图片用自定义组件写,大图片在父组件身上点击小图片,父组件的图片跟着变化

图片如下:

第一种: 子组件向父组件传参写法

@Component
struct MyButtom {
  imgUrl:string[]= ['111','2','3','4']
  Url:(n:string)=>void=()=>{}
  build() {
    Row() {
      ForEach(this.imgUrl,(item:string,index:number)=>{
        Image($r(`app.media.${item}`))
          .width(80)
          .onClick(()=>{
            this.Url(this.imgUrl[index])
          })
      })
    }
  }
}
@Entry
@Component
struct Index {
  @State img:string = '111'
  igUrl=(n:string)=>{
    this.img = n
}
  build() {
    Column({ space: 10 }) {
      MyButtom({Url:this.igUrl})
      Image($r(`app.media.${this.img}`))
        .width('100%')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

第二种: 自定义组件的事件写法

@Component
struct MyImg {
  // 接收图片地址
  imgSrc: string = "app.media.111"
  build() {
    Image($r(this.imgSrc))
      .width(50)
      .aspectRatio(1)
      .borderRadius(25)
  }
}
@Entry
@Component
struct Index {
  // 小图地址数组
  @State
  imgList: string[] = ["app.media.111", "app.media.2", "app.media.3", "app.media.4"]
  // 大图路径
  @State
  bigSrc: string = "app.media.111"
  build() {
    Column() {
      //   大图
      Image($r(this.bigSrc))
        .width(200)
      //   小图
      Row() {
        ForEach(this.imgList, (item: string) => {
          MyImg({ imgSrc: item })
            .width(40)
            .height(40)
            .onClick(() => {
              this.bigSrc = item
            })
        })
      }
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}