什么是双向绑定

双向绑定指的是在组件间数据的双向绑定。当一个值无论是在父组件还是子组件中改动都会在这两层中都更新界面。

回顾过往的“双向绑定”实现方式

靠@Event装饰回调函数

一般是对于@Param修饰的状态变量。当子组件发生某个动作的时候,调用某个父组件传递过来的回调函数。以让父组件在自己的逻辑中更新它的状态变量,从而触发@Param的单向更新。代码如下:

import hilog from '@ohos.hilog'
 
const  LOG_DOMAIN:number = 0x0001
const LOG_TAG:string = 'EventTest'
@Entry
@ComponentV2
struct EventTest {
  @Local mTitle:string = "Title one"
  @Local mFontColor:Color = Color.Red
  build() {
    Column(){
      Child1({
        mTitle: this.mTitle,
        mFontColor: this.mFontColor,
        changeFactory: (x:number)=>{
          if (x == 1) {
            this.mTitle = "Title One";
            this.mFontColor = Color.Red;
          } else if (x == 2) {
            this.mTitle = "Title Two";
            this.mFontColor = Color.Green;
          }
          hilog.info(LOG_DOMAIN, LOG_TAG, `father: mTitle=${this.mTitle}, mFontColor=${this.mFontColor}`)
        }
      })
    }
    .height('100%')
    .width('100%')
  }
}
 
@ComponentV2
struct Child1{ //ArkUI中的组件名不能重名,我们之前的例子中已经写过Child的组件了.这次加上个1
  @Param mTitle:string = ""
  @Param mFontColor:Color = Color.Black
  @Event changeFactory:(x:number)=>void
  build() {
    Column(){
      Text(`${this.mTitle}`)
        .fontColor(this.mFontColor)
      Button("change to Title Two")
        .onClick(() => {
          this.changeFactory(2);
          hilog.info(LOG_DOMAIN, LOG_TAG, `child: mTitle=${this.mTitle}, mFontColor=${this.mFontColor}`)
        })
      Button("change to Title One")
        .onClick(() => {
          this.changeFactory(1);
          hilog.info(LOG_DOMAIN, LOG_TAG, `child: mTitle=${this.mTitle}, mFontColor=${this.mFontColor}`)
        })
    }
  }
}
注意

子组件通过@Event而触发父组件去修改值,相关的数据源在父组件逻辑内修改的时候就立即生效了,但是,同步到子组件的时候却是异步处理的,所以子组件的那个绑定的状态变量的值并不会立即发生变化。这是因为@Event将子组件值实际的变化能力交由父组件来处理,在父组件决定如何处理后,将最终的值在渲染之前同步给子组件。

所以当用户点击change to Title Two按钮的时候,上方代码的打印结果为下:

靠@Provider和@Consume装饰器实现跨组件双向绑定

@Entry
@ComponentV2
struct ProviderConsumerTest {
  @Provider() str: string = 'hello'
  build() {
    Column() {
      Button(this.str)
        .onClick(()=>{
          this.str += 0
        })

      Child2()
    }
    .height('100%')
      .width('100%')
  }
}
 
@ComponentV2
struct Child2{
  @Consumer() str:string = "world" //没写参数,则意味着要找父组件中叫 str 的变量
  build() {
    Column(){
      Button(this.str)
        .onClick(()=>{
          this.str += 0
        })
    }
  }
}

关于!!语法糖涉及的双向绑定。

!!语法糖是一个和@Param和@Event非常相似,但是又能省去一部分代码,书写起来比较方便的一种方式。它通常就是来处理那些回调里面仅仅就是赋值的操作。所以如果您的代码中,回调仅仅就是赋值逻辑,那么直接用语法糖就行! 例如如果之前的回调函数的写法是:

也就是表面上您可以理解为极简回调(@Event其实就是装饰了一个函数,该函数被子组件拿来当回调用的)。不过这是表象。二者内部还有一点点区别。

还记得我们将@Event的时候,有一个关于数据同步上的细节么?下面再讲一下。如果想看详细内容请参照HarmonyOS: ArkUI V2装饰器-@Event:规范组件输出-CSDN博客

@Event与@Param的数据同步缺陷

在上文中我们提到了@Event使用的时候,需要注意的点。大体就是,当子组件某行为触发回调函数调用的时候,父组件会在回调函数中实现改元数据值的操作,但是这个值同步给子组件的时候,是在子线程里面执行的。也就是有一定的时延。 这种情况在极其精微的逻辑场景中有时候会出现问题的。但是!!语法糖是可以保证父组件和子组件之间的数据是原子级更新的。

总结-!!语法糖为什么存在
  • !!语法糖能完成双向绑定
  • !!语法糖更省代码,写起来简洁。
  • 其数据同步是原子级的。不用担心什么多线程引发的问题。

!!语法糖的使用

我们先列代码熟悉下是怎么用的吧。


@Entry
@ComponentV2
struct TwoWayBindTest {
  @Local value:number = 0
  build() {
    Column(){
      Text(`${this.value}`)
      Button('change value')
        .onClick(()=>{
          this.value ++
        })

      Star({value: this.value!!}) //后面一定要加双感叹号。这样就免去写赋值的逻辑了。
    }
    .width('100%')
    .height('100%')
  }
}

@ComponentV2
struct Star{
  // 这两个属性名必须一致,且@Event修饰的函数变量必须加$前缀。便于编译的时候识别。
  @Param value: number = 0 
  @Event $value: (val: number)=>void = (val: number)=>{} //默认值
  build() {
    Column(){
      Text(`${this.value}`)
      Button(`change value`)
        .onClick(()=>{
          this.$value(10)
        })
    }
  }
}

!!语法糖对于系统组件双向绑定

当有些系统组件,内部含有一些比较重要的,外界也需要感知的状态变量时,您在使用此系统组件的时候,用语法糖传入变量。但是并非所有组件所有变量都支持,我们捡一个使用频次多的场景来讲明, 部分功能是支持的。您写代码的时候注意一下,如果支持,可以写的简洁一些。

@Entry
@ComponentV2
struct TwoWayBindTest {
  @Local isShow:boolean = false
  build() {
    Column(){
      Row(){
        Text(`点击展示菜单`)
          .bindMenu(this.isShow!!, [
            {value: '1', action: ()=>{console.info('点击了菜单1')}},
            {value: '2', action: ()=>{console.info('点击了菜单2')}}
          ])
      }
      .height('50%')

      Text("当前isShow: " + this.isShow).fontSize(18).fontColor(Color.Red)
      Row() {
        Button("Click")
          .onClick(() => {
            this.isShow = !this.isShow;
          })
          .width(100)
          .fontSize(20)
          .margin(10)
      }
    }
    .width('100%')
    .height('100%')
  }
}

使用限制

!!双向绑定语法不支持多层父子组件传递。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐