鸿蒙开发之组件通信

状态共享-父子单向

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

Prop 支持类型和State修饰符基本一致,并且**Prop可以给初始值,也可以不给**

完成父-子的单向同步
@Entry
@Component
struct PropCase {
  @State pnum: number = 0
  build() {
    Row() {
      Column() {
        Text(this.pnum.toString())
        Button("+1")
          .onClick(() => {
            this.pnum++
          })

        Divider()
        Child({ num: this.pnum })
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct Child {
  @Prop num: number
  build() {
    Column() {
      Text("子组件")
      Text(this.num.toString())
    }.height(60)
    .width('100%')
    .backgroundColor(Color.Pink)
  }
}

  • 如果子组件修改这个Prop呢?我们来试试
Button("修改子组件Prop")
 .onClick(() => {
  this.num++
 })

  • 我们发现使用Prop修饰的状态,只会在当前子组件生效,不会传导到父组件,所以它属于一种单向传递
  • 子组件可修改 Prop 数据值,但不同步到父组件,父组件更新后覆盖子组件 Prop 数据
  • 子组件可以初始化默认值
  • Prop如果传递是对象类型,它只会在子组件内部生效,不会延伸到父组件-
网络相册案例
  • 点个按钮,出现选择照片的相册
  • 选择完成之后, 点击完成图片回显示到页面
  • 基础相册封装和使用
import { GoodItem } from '../04/models'
@Entry
@Component
struct PropBigCase {
  @State
  showAlbum: boolean = false
  @State
  list: GoodItem[] = [
    {
      "id": 1,
      "goods_name": "班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣",
      "goods_img": "assets/1.webp",
      "goods_price": 108,
      "goods_count": 1,
    },
    {
      "id": 2,
      "goods_name": "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
      "goods_img": "assets/2.webp",
      "goods_price": 129,
      "goods_count": 1,
    },
    {
      "id": 3,
      "goods_name": "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
      "goods_img": "assets/3.webp",
      "goods_price": 198,
      "goods_count": 1,
    },
    {
      "id": 4,
      "goods_name": "思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套",
      "goods_img": "assets/4.webp",
      "goods_price": 99,
      "goods_count": 1,
    },
    {
      "id": 5,
      "goods_name": "幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮",
      "goods_img": "assets/5.webp",
      "goods_price": 156,
      "goods_count": 1,
    },
    {
      "id": 6,
      "goods_name": "ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女",
      "goods_img": "assets/6.webp",
      "goods_price": 142.8,
      "goods_count": 1,
    },
    {
      "id": 7,
      "goods_name": "幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套",
      "goods_img": "assets/7.webp",
      "goods_price": 219,
      "goods_count": 2,
    },
    {
      "id": 8,
      "goods_name": "依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套",
      "goods_img": "assets/8.webp",
      "goods_price": 178,
      "goods_count": 1,
    },
    {
      "id": 9,
      "goods_name": "芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬",
      "goods_img": "assets/9.webp",
      "goods_price": 128,
      "goods_count": 1,
    },
    {
      "id": 10,
      "goods_name": "Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫",
      "goods_img": "assets/10.webp",
      "goods_price": 153,
      "goods_count": 1,
    }
  ]

  build() {
     Column() {
       Button("选择图片")
         .onClick(() => {
           this.showAlbum = true
         })
       if(this.showAlbum) {
         PhotoAlbum({
           list: this.list,
           maxSelectNumber: 2,
           close: () => {
             this.showAlbum = false
           }
         })
       }
     }
    .width('100%')
    .height('100%')
    .padding(2)
  }
}

@Component
struct PhotoAlbum {
  @Prop
  maxSelectNumber: number = 9 // 设置可选择的图片的张数
  @Prop
  list: GoodItem[] = []
  close: () => void = () => {}
  build() {
    Column() {
      Grid() {
        // 数据的
        ForEach(this.list, (item: GoodItem) => {
          GridItem() {
            Image(item.goods_img)
              .aspectRatio(1)
              .onClick(() => {
                // 通过一个标记 能够知道当前的图片到底是选中还是没选中 如果选中 取消选中
                // 如果没选中 则选中 可以设置最多选择9张图片
              })
          }
        })
      }
      .columnsGap(2)
      .rowsGap(2)
      .columnsTemplate("1fr 1fr 1fr")
      .layoutWeight(1)
      Row() {
         Button("取消")
           .onClick(() => {
             this.close()
           })
           .backgroundColor(Color.Gray)
         Text(`可选${this.maxSelectNumber}`)
        Button("完成")
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .padding({
        left: 10,
        right: 10
      })
      .height(50)
      .width('100%')

    }
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
    .position({
      x: 0,
      y: 0
    })
  }
}

  • 相册选择
import { GoodItem } from '../04/models'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct PropBigCase {
  @State
  showAlbum: boolean = false
  @State
  list: GoodItem[] = [
    {
      "id": 1,
      "goods_name": "班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣",
      "goods_img": "assets/1.webp",
      "goods_price": 108,
      "goods_count": 1,
    },
    {
      "id": 2,
      "goods_name": "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
      "goods_img": "assets/2.webp",
      "goods_price": 129,
      "goods_count": 1,
    },
    {
      "id": 3,
      "goods_name": "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
      "goods_img": "assets/3.webp",
      "goods_price": 198,
      "goods_count": 1,
    },
    {
      "id": 4,
      "goods_name": "思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套",
      "goods_img": "assets/4.webp",
      "goods_price": 99,
      "goods_count": 1,
    },
    {
      "id": 5,
      "goods_name": "幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮",
      "goods_img": "assets/5.webp",
      "goods_price": 156,
      "goods_count": 1,
    },
    {
      "id": 6,
      "goods_name": "ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女",
      "goods_img": "assets/6.webp",
      "goods_price": 142.8,
      "goods_count": 1,
    },
    {
      "id": 7,
      "goods_name": "幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套",
      "goods_img": "assets/7.webp",
      "goods_price": 219,
      "goods_count": 2,
    },
    {
      "id": 8,
      "goods_name": "依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套",
      "goods_img": "assets/8.webp",
      "goods_price": 178,
      "goods_count": 1,
    },
    {
      "id": 9,
      "goods_name": "芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬",
      "goods_img": "assets/9.webp",
      "goods_price": 128,
      "goods_count": 1,
    },
    {
      "id": 10,
      "goods_name": "Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫",
      "goods_img": "assets/10.webp",
      "goods_price": 153,
      "goods_count": 1,
    }
  ]

  build() {
     Column() {
       Button("选择图片")
         .onClick(() => {
           this.showAlbum = true
         })
       if(this.showAlbum) {
         PhotoAlbum({
           list: this.list,
           maxSelectNumber: 9,
           close: () => {
             this.showAlbum = false
           }
         })
       }
     }
    .width('100%')
    .height('100%')
    .padding(2)
  }
}

@Component
struct PhotoAlbum {
  @Prop
  maxSelectNumber: number = 9 // 设置可选择的图片的张数
  @Prop
  list: GoodItem[] = []
  @State
  selectPhotos: SelectPhoto[] = []
  close: () => void = () => {}

  // 用来选中或者取消选中图片
  selectImage (item: GoodItem) {
    // 通过一个标记 能够知道当前的图片到底是选中还是没选中 如果选中 取消选中
    // 如果没选中 则选中 可以设置最多选择9张图片
    const index = this.selectPhotos.findIndex(obj => obj.imgId === item.id)
    if(index > -1) {
      // 表示已经选择了 选中的化需要移除
      // 数组移除
      // 先找索引
      // 再通过吧splice进行移除
      this.selectPhotos.splice(index, 1) // 移除一个内容
      promptAction.showToast({ message: '执行移除' })
    }
    else {
      // 当选择张数小于最大张数时才可以继续
      if(this.selectPhotos.length < this.maxSelectNumber) {
        // 没有选中
        this.selectPhotos.push({ imgUrl: item.goods_img, imgId: item.id })
      }
    }
  }
  // 获取是否显示对号
  getShowSelect (item: GoodItem) {
   return  this.selectPhotos.findIndex(obj => obj.imgId === item.id) > -1
  }
  build() {
    Column() {
      Grid() {
        // 数据的
        ForEach(this.list, (item: GoodItem) => {
          GridItem() {
            Stack({ alignContent: Alignment.BottomEnd }) {
              Image(item.goods_img)
                .aspectRatio(1)

                if(this.getShowSelect(item)) {
                  // 需要展示对号
                  Image($r("app.media.select"))
                    .width(60)
                    .height(60)
                    .fillColor(Color.Orange)
                }
            }
            .onClick(() => {
              // 通过一个标记 能够知道当前的图片到底是选中还是没选中 如果选中 取消选中
              // 如果没选中 则选中 可以设置最多选择9张图片
              this.selectImage(item)
            })
          }
        })
      }
      .columnsGap(2)
      .rowsGap(2)
      .columnsTemplate("1fr 1fr 1fr")
      .layoutWeight(1)
      Row() {
         Button("取消")
           .onClick(() => {
             this.close()
           })
           .backgroundColor(Color.Gray)
         Text(`已选${this.selectPhotos.length}/ 可选${this.maxSelectNumber}`)
        Button("完成")
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .padding({
        left: 10,
        right: 10
      })
      .height(50)
      .width('100%')

    }
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
    .position({
      x: 0,
      y: 0
    })
  }
}

interface SelectPhoto {
  imgUrl: string | ResourceColor
  imgId: number
}

新的诉求
  • 希望点击图片-完成图片的预览 需要使用弹出层- 两种的使用方式- dialog - sheet
  • 弹窗UI的第一种方式CustomDialog
  • struct这个结构体只能被 Component和CustomDialog修饰
  • 必须被CustomDialog修饰
  • 组件中必须有一个属性 它的类型是 CustomDialogController,名字其实无所谓
  • Component的修饰符可以没有,有的话意味着它可以作为组件使用
@CustomDialog
struct PreviewDialog {
  controller: CustomDialogController // 控制器
  url: ResourceStr = ""
  build() {
    Column() {
      Image(this.url)
        .width('100%')
    }.backgroundColor(Color.Black)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width("100%")
    .onClick(() => {
        this.controller.close()
    })
  }
}
  • 如果需要使用弹层,需要在使用的组件或者页面中显示的声明一个对象
// 显示声明对象 类型未CustomDialogController
  preview: CustomDialogController = new CustomDialogController({
    builder: PreviewDialog({ url: this.selectImg }), // 这里需要传入 自定义弹层的对象
    customStyle: true  // 按照弹层的样式来渲染
  })

:::success
customStyle的意思是否允许自定义的样式设置,因为默认的弹层是有一些定制的样式的。

:::

  • 弹层就两个方法

:::success
open- 创建弹层组件-显示-会有动画的弹出

close- 销毁组件-推出-会有动画的退出

:::

  • 涉及到Dialog的传值

:::success
因为open/close会创建和销毁组件,不存在缓存现象,里面的参数实际上没有任何必要用修饰符

:::

  • 在父组件中调用打开
GridItem() {
             Image(item.imgUrl)
               .aspectRatio(1)
               .borderRadius(4)
               .onClick(() => {
                 this.selectImg = item.imgUrl
                 // 弹出层 显示图片
                  this.preview.open()
               })
           }
  • 在本身组件中同样是可以打开和关闭
.onClick(() => {
        this.controller.close()
    })
  • sheet的用法

:::success

  • 弹出框-类似于ios的交互-底部推到屏幕中的一个区域-可能有半屏或者全屏的区域
  • 不需要使用组件
  • 使用的是通用属性 bindSheet,可以通过该属性设置弹出框的 变量控制,内容控制,属性控制

:::

.bindSheet($$this.showAlbum, this.getSheetBuilder(), {
      showClose: false
    })

:::success
$$ 为什么在这里使用?

因为需要数据驱动视图,视图发生变化,它同样需要更新数据

:::

  • 视图内容
// 渲染相册的内容
  // Entry修饰的方法 可以没有根节点 但是 如果有的话 必须该组件是个容器组件
  @Builder
  getSheetBuilder() {
    // 坑点 bindSheet的内容最外层需要用原生组件才可以
    Column() {
      PhotoAlbum({
        list: this.list,
        maxSelectNumber: 5,
        close: () => {
           this.showAlbum = false
        },
        finish: (selectPhotos: SelectPhoto[]) => {
          this.selectList.push(...selectPhotos)
          this.showAlbum = false
        }
      })
    }
  }

:::success
诉求: 需要弹出的照片需要进行滑动- Swiper 轮播图

就是一个组件Swiper, 自动的可以实现滑动, 可以实现上下和左右, 可以双向绑定组件的当前的激活索引 index

:::

@CustomDialog
struct PreviewDialog {
  controller: CustomDialogController // 控制器
  urls: ResourceStr [] = []
  @State
  selectIndex: number  = 0
  build() {
    Column() {
      Swiper() {
        ForEach(this.urls, (url: string) => {
          Image(url)
            .width('100%')
        })
      }.index($$this.selectIndex)
    }
    .backgroundColor(Color.Black)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width("100%")
    .onClick(() => {
      this.controller.close()
    })
  }
}
  • 相册综合案例的完整代码
import { GoodItem } from '../04/models'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct PropBigCase {
  @State
  showAlbum: boolean = false
  @State
  list: GoodItem[] = [
    {
      "id": 1,
      "goods_name": "班俏BANQIAO超火ins潮卫衣女士2020秋季新款韩版宽松慵懒风薄款外套带帽上衣",
      "goods_img": "assets/1.webp",
      "goods_price": 108,
      "goods_count": 1,
    },
    {
      "id": 2,
      "goods_name": "嘉叶希连帽卫衣女春秋薄款2020新款宽松bf韩版字母印花中长款外套ins潮",
      "goods_img": "assets/2.webp",
      "goods_price": 129,
      "goods_count": 1,
    },
    {
      "id": 3,
      "goods_name": "思蜜怡2020休闲运动套装女春秋季新款时尚大码宽松长袖卫衣两件套",
      "goods_img": "assets/3.webp",
      "goods_price": 198,
      "goods_count": 1,
    },
    {
      "id": 4,
      "goods_name": "思蜜怡卫衣女加绒加厚2020秋冬装新款韩版宽松上衣连帽中长款外套",
      "goods_img": "assets/4.webp",
      "goods_price": 99,
      "goods_count": 1,
    },
    {
      "id": 5,
      "goods_name": "幂凝早秋季卫衣女春秋装韩版宽松中长款假两件上衣薄款ins盐系外套潮",
      "goods_img": "assets/5.webp",
      "goods_price": 156,
      "goods_count": 1,
    },
    {
      "id": 6,
      "goods_name": "ME&CITY女装冬季新款针织抽绳休闲连帽卫衣女",
      "goods_img": "assets/6.webp",
      "goods_price": 142.8,
      "goods_count": 1,
    },
    {
      "id": 7,
      "goods_name": "幂凝假两件女士卫衣秋冬女装2020年新款韩版宽松春秋季薄款ins潮外套",
      "goods_img": "assets/7.webp",
      "goods_price": 219,
      "goods_count": 2,
    },
    {
      "id": 8,
      "goods_name": "依魅人2020休闲运动衣套装女秋季新款秋季韩版宽松卫衣 时尚两件套",
      "goods_img": "assets/8.webp",
      "goods_price": 178,
      "goods_count": 1,
    },
    {
      "id": 9,
      "goods_name": "芷臻(zhizhen)加厚卫衣2020春秋季女长袖韩版宽松短款加绒春秋装连帽开衫外套冬",
      "goods_img": "assets/9.webp",
      "goods_price": 128,
      "goods_count": 1,
    },
    {
      "id": 10,
      "goods_name": "Semir森马卫衣女冬装2019新款可爱甜美大撞色小清新连帽薄绒女士套头衫",
      "goods_img": "assets/10.webp",
      "goods_price": 153,
      "goods_count": 1,
    }
  ]
  @State
  selectList: SelectPhoto[] = []
  selectImg: ResourceStr = ''

  selectIndex: number = 0

  // 显示声明对象 类型未CustomDialogController
  preview: CustomDialogController = new CustomDialogController({
    builder: PreviewDialog({ urls: this.selectList.map(item => item.imgUrl), selectIndex: this.selectIndex }), // 这里需要传入 自定义弹层的对象
    customStyle: true, // 按照弹层的样式来渲染
    cancel: () => {

    }
  })
  // 渲染相册的内容
  // Entry修饰的方法 可以没有根节点 但是 如果有的话 必须该组件是个容器组件
  @Builder
  getSheetBuilder() {
    // 坑点 bindSheet的内容最外层需要用原生组件才可以
    Column() {
      PhotoAlbum({
        list: this.list,
        maxSelectNumber: 5,
        close: () => {
           this.showAlbum = false
        },
        finish: (selectPhotos: SelectPhoto[]) => {
          this.selectList.push(...selectPhotos)
          this.showAlbum = false
        }
      })
    }
  }

  build() {
    Column({ space: 10 }) {
      Button("选择图片")
        .onClick(() => {
          this.showAlbum = true
        })

      // 渲染选择的图片
      Grid() {
        ForEach(this.selectList, (item: SelectPhoto, index: number) => {
          GridItem() {
            Image(item.imgUrl)
              .aspectRatio(1)
              .borderRadius(4)
              .onClick(() => {
                 this.selectIndex = index // 记录点击的索引
                // 弹出层 显示图片
                this.preview.open()
              })
          }
        })
      }
      .columnsTemplate("1fr 1fr")
      .columnsGap(2)
      .rowsGap(2)
    }
    .width('100%')
    .height('100%')
    .padding(2)
    .bindSheet($$this.showAlbum, this.getSheetBuilder(), {
      showClose: false,
      height: '70%'
    })
    // 数据变化 可以驱动视图的更新
    // 视图的更新无法驱动数据的变化
  }
}

@Component
struct PhotoAlbum {
  @Prop
  maxSelectNumber: number = 9 // 设置可选择的图片的张数
  @Prop
  list: GoodItem[] = []
  @State
  selectPhotos: SelectPhoto[] = []
  close: () => void = () => {
  }
  finish: (selectPHOTOES: SelectPhoto[]) => void = () => {
  }

  // 用来选中或者取消选中图片
  selectImage(item: GoodItem) {
    // 通过一个标记 能够知道当前的图片到底是选中还是没选中 如果选中 取消选中
    // 如果没选中 则选中 可以设置最多选择9张图片
    const index = this.selectPhotos.findIndex(obj => obj.imgId === item.id)
    if (index > -1) {
      // 表示已经选择了 选中的化需要移除
      // 数组移除
      // 先找索引
      // 再通过吧splice进行移除
      this.selectPhotos.splice(index, 1) // 移除一个内容
      promptAction.showToast({ message: '执行移除' })
    }
    else {
      // 当选择张数小于最大张数时才可以继续
      if (this.selectPhotos.length < this.maxSelectNumber) {
        // 没有选中
        this.selectPhotos.push({ imgUrl: item.goods_img, imgId: item.id })
      }
    }
  }

  // 获取是否显示对号
  getShowSelect(item: GoodItem) {
    return this.selectPhotos.findIndex(obj => obj.imgId === item.id) > -1
  }

  build() {
    Column() {
      Row() {
        Button("取消")
          .onClick(() => {
            this.close()
          })
          .backgroundColor(Color.Gray)
        Text(`已选${this.selectPhotos.length}/可选${this.maxSelectNumber}`)
        Button("完成")
          .onClick(() => {
            this.finish(this.selectPhotos)
          })
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .padding({
        left: 10,
        right: 10
      })
      .zIndex(40)
      .height(50)
      .width('100%')
      Grid() {
        // 数据的
        ForEach(this.list, (item: GoodItem) => {
          GridItem() {
            Stack({ alignContent: Alignment.BottomEnd }) {
              Image(item.goods_img)
                .aspectRatio(1)

              if (this.getShowSelect(item)) {
                // 需要展示对号
                Image($r("app.media.select"))
                  .width(60)
                  .height(60)
                  .fillColor(Color.Orange)
              }
            }
            .onClick(() => {
              // 通过一个标记 能够知道当前的图片到底是选中还是没选中 如果选中 取消选中
              // 如果没选中 则选中 可以设置最多选择9张图片
              this.selectImage(item)
            })
          }
        })
      }
      .columnsGap(2)
      .rowsGap(2)
      .columnsTemplate("1fr 1fr 1fr")
      .layoutWeight(1)



    }
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%')
    .height('100%')
    .backgroundColor(Color.White)
  }
}

interface SelectPhoto {
  imgUrl: string | ResourceStr
  imgId: number
}

//

@CustomDialog
struct PreviewDialog {
  controller: CustomDialogController // 控制器
  urls: ResourceStr [] = []
  @State
  selectIndex: number  = 0
  build() {
    Column() {
      Swiper() {
        ForEach(this.urls, (url: string) => {
          Image(url)
            .width('100%')
        })
      }.index($$this.selectIndex)
    }
    .backgroundColor(Color.Black)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width("100%")
    .onClick(() => {
      this.controller.close()
    })
  }
}

2. 状态共享-父子双向

  • Prop修饰符- 父组件数据更新-让子组件更新- 子组件更新-父组件不为所动

:::info
Prop是单向的,而Link修饰符则是双向的数据传递,只要使用Link修饰了传递过来的数据,这个时候就是双向同步了

注意点: 在父组件传入Link属性时,需要使用$来修饰该变量,去掉this

Next版本中可以不用$修饰,直接使用this.xx属性绑定即可

Link修饰符不允许给初始值

:::

  • 将刚刚的案例改造成双向的

子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。

@Entry
@Component
struct LinkCase {
  @State
  num: number = 0
  build() {
    Column({ space: 20 }) {
      Row ({ space: 10 }) {
        Text("父组件")
          .fontSize(40)
        Text(this.num.toString())
          .fontSize(40)

      }
      .onClick(() => {
        this.num++
      })

      Divider()
        .width("100%")
        .strokeWidth(20)
      LinkChild({ num: this.num })
      LinkChild({ num: $num })
      // Link的修饰符传值 在Next版本这一代 进行了一代的放宽
      // $num
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
  }
}

@Component
struct LinkChild {
  @Link
  num: number
  build() {
    Column() {
      Text("子组件" + this.num)
        .onClick(() => {
          this.num++
        })
      Swiper() {
        ForEach([1,2,3,4,5,6,7,8,9,10], (item: number) => {
          Row() {
            Text(item.toString())
              .fontColor(Color.White)
              .fontSize(50)
          }
          .justifyContent(FlexAlign.Center)
          .width('100%')
          .height(130)
          .backgroundColor(Color.Blue)
        })
      }
      .index($$this.num)
    }

  }
}

:::success
Link修饰符的要求- 你的父组件传值时传的必须是个响应式状态

State/Prop/

:::

:::info
需要注意的是,Link修饰的变量类型和State和Prop的类型是一样的,支持string、number、boolean、enum Object Class以及这些类型对应的数组

:::

:::success
当我们的Link,比如是一个数组的时候, 如果你循环该数组,还有一层组件,此时该组件的中的属性不能再使用Link来修饰了,因为语法不支持Link被循环之后的 传递

:::

:::info
大家一定在想,为什么不把每个菜封装成一个组件,然后用Link传递过去岂不是更方便???

我们试试

:::

:::info
看到没有,ArtTS不支持这么做,也就是Link修饰的数据必须得是最外层的 State数据,想要实现我们刚刚的设想,我们还得另辟蹊径。-后续ObjectLink 和Observerd会解决这个问题

:::

3. 状态共享-后代组件

:::info
如果我们的组件层级特别多,ArkTS支持跨组件传递状态数据来实现双向同步@Provide和 @Consume

这特别像Vue中的依赖注入

:::

  • 假设我们有三层组件,Index-Child-Grand, Index的数据不想经过Child而直接给到Grand可以使用该修饰器
@Entry
@Component
struct ProvideCase02 {
 @Provide count: number = 0

  build() {
    Row() {
      Column({ space: 15 }) {
        Text(this.count.toString())
          .fontSize(50)
        Button("顶级组件+1")
          .onClick(() => {
            this.count++
          })
        Divider()
        Child()
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct Child {
  build() {
    Column() {
      Text("子组件")
        .fontSize(40)
      Divider()
      Grand()
    }
  }
}

@Component
struct Grand {
  @Consume count: number
  build() {
    Column() {
      Text("孙组件")
        .fontSize(30)
      Text(this.count.toString())
    }
  }
}

:::info
注意: 在不指定Provide名称的情况下,你需要使用相同的名字来定义和接收数据

:::

1)通过相同的变量名绑定

@Entry
@Component
struct Index {
  @Provide
  money: number = 0

  build() {
    Column({ space: 20 }) {
      Text('父组件:' + this.money)
        .onClick(() => {
          this.money++
        })
      Parent()
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct Parent {
  @Consume
  money: number

  build() {
    Column({ space: 20 }) {
      Text('父组件:' + this.money)
        .onClick(() => {
          this.money++
        })
      Child()
    }
  }
}

@Component
struct Child {
  @Consume
  money: number

  build() {
    Text('子组件:' + this.money)
      .onClick(() => {
        this.money++
      })
  }
}

:::info

  • Object、class、string、number、boolean、enum 类型均支持
  • 通过相同的变量别名绑定 @Provide('key')@Consume('key') key需要保持一致

:::

:::color2
ArkTS所有内容都不支持深层数据更新 UI渲染

:::

  • 综合案例

:::success
声明一个登录页

:::

import { promptAction, router } from '@kit.ArkUI'

@Entry
@Component
struct ProvideBigCase {
  @State
  phone: string = "13812345678"
  @State
  code: string = "123456"

  getBtnEnable () {
    return  !!(this.phone && this.code)
  }
  login () {
    if(this.phone === "13812345678" && this.code === '123456') {
      // 需要跳入主页
        router.pushUrl({
          url: 'pages/09/ProvideBigMainCase'  // 一定是个Page
        })
    }else {
      promptAction.showToast({ message: '手机号或验证码错误' })
    }
  }
  build() {
    Column({ space: 20 }) {
        Image($r("app.media.login_logo"))
          .width(80)
          .aspectRatio(1)
        Text("欢迎使用鸿蒙刷题")
          .fontSize(20)
        TextInput({
          text: $$this.phone,
          placeholder: '请输入手机号'
        })
          .height(50)
          .width("100%")
        TextInput({
          text: $$this.code,
          placeholder: '请输入验证码'
        })
          .height(50)
          .width("100%")
      Button("登录")
        .enabled(this.getBtnEnable())
        .height(50)
        .width('100%')
        .onClick(() => {
          this.login()
        })
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .padding({
      left: 40,
      right: 40
    })
  }
}
// 登录 -》 主页 =》 拿到身份信息 =》 通过三层传递 =》 将身份信息传到每一层组件

  • 跳转到主页
import { UserInfo, UserInfoModel } from './models/user'

@Entry
@Component
struct ProvideBigMainCase {
  // 通过Provide 和Consume来实现 数据的传递
  @Provide
  user: UserInfoModel = new UserInfoModel({} as UserInfo)

  // 进入页面就执行的函数
  aboutToAppear(): void {
    this.getCurrentUser()
  }
  // 获取当前的用户信息
  async getCurrentUser() {
    // 请求
    const result = await new Promise<UserInfo>((resolve) => {
      setTimeout(() => {
        resolve({
          id: 1,
          avatar: '456',
          mobile: '13812345678',
          email: '123@qq.com',
          nickName: '水若寒宇'
        })
      }, 1000)
    })
    this.user = new UserInfoModel(result)
  }

  build() {
    // Tabs
    Tabs() {
      TabContent() {
        WeChat()
      }.tabBar("微信")

      TabContent() {
        Connect()
      }.tabBar("联系人")

      TabContent() {
        Discover()
      }.tabBar("发现")

      TabContent() {
        My()
      }.tabBar("我的")
    }.barPosition(BarPosition.End)
  }
}

@Component
struct WeChat {
  @Consume
  user: UserInfoModel
  build() {
   Column() {
     Text(this.user.nickName)
   }
  }
}

@Component
struct Connect {
  @Consume
  user: UserInfoModel
  build() {
    Column() {
      Text(this.user.mobile)
    }
  }
}

@Component
struct My {
  @Consume
  user: UserInfoModel
  build() {
    Text(this.user.email)
  }
}

@Component
struct Discover {
  @Consume
  user: UserInfoModel
  build() {
    Text(this.user.avatar)
  }
}

:::success
新的知识点

router.pushUrl() . 可以实现路由的跳转-强调-地址上第一个/必须去掉,必须保证跳转的页面是Entry修饰的页面,并且在 resources/base/profile/main_pages.json中可以找到这个地址

:::

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值