HarmonyOS鸿蒙Next中ArkUI自定义弹框Bug

HarmonyOS鸿蒙Next中ArkUI自定义弹框Bug 将自定义弹框CustomDialogController放入数组中时,通过索引open()能正常打开,但是弹框内部调用this.controller.close()关闭会报undefined导致app异常退出,100%复现!!!不使用数组没问题!

Deveco和SDK均使用6.0.1,测试用的6.0.1系统模拟器

4 回复

开发者您好,本地使用如下demo未在6.0.1模拟器复现问题,demo中第二个弹窗放入数组中,可以打开也可以关闭。也有可能是模拟的场景与您的有差异,请提供一下能复现问题的demo,方便定位问题。感谢您的理解与支持。

// CustomDialogUser.ets
@CustomDialog
struct CustomDialogExampleTwo {
  controllerTwo?: CustomDialogController
  build() {
    Column() {
      Text('我是第二个弹窗')
        .fontSize(30)
        .height(100)
      Button('点我关闭第二个弹窗')
        .onClick(() => {
          if (this.controllerTwo != undefined) {
            this.controllerTwo.close()
          }
        })
        .margin(20)
    }
  }
}
@CustomDialog
@Component
struct CustomDialogExample1 {
  @Link textValue: string
  @Link inputValue: string
  dialogControllerTwoArr:CustomDialogController[] = [];
  dialogControllerTwo: CustomDialogController   = new CustomDialogController({
    builder: CustomDialogExampleTwo(),
    alignment: DialogAlignment.Bottom,
    onWillDismiss:(dismissDialogAction: DismissDialogAction)=> {
      console.info("reason=" + JSON.stringify(dismissDialogAction.reason))
      console.log("dialog onWillDismiss")
      if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
        dismissDialogAction.dismiss()
      }
      if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
        dismissDialogAction.dismiss()
      }
    },
    offset: { dx: 0, dy: -25 } })
  controller?: CustomDialogController
  // 若尝试在CustomDialog中传入多个其他的Controller,以实现在CustomDialog中打开另一个或另一些CustomDialog,那么此处需要将指向自己的controller放在所有controller的后面
  cancel: () => void = () => {
  }
  confirm: () => void = () => {
  }

  aboutToAppear(): void {
    this.dialogControllerTwoArr[0] = this.dialogControllerTwo;
  }

  build() {
    Column() {
      Text('Change text').fontSize(20).margin({ top: 10, bottom: 10 })
      TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
        .onChange((value: string) => {
          this.textValue = value
        })
      Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        Button('cancel')
          .onClick(() => {
            if (this.controller != undefined) {
              this.controller.close()
              this.cancel()
            }
          }).backgroundColor(0xffffff).fontColor(Color.Black)
        Button('confirm')
          .onClick(() => {
            if (this.controller != undefined) {
              this.inputValue = this.textValue
              this.controller.close()
              this.confirm()
            }
          }).backgroundColor(0xffffff).fontColor(Color.Red)
      }.margin({ bottom: 10 })

      Button('点我打开第二个弹窗')
        .onClick(() => {
          if (this.dialogControllerTwo != null) {
            // this.dialogControllerTwo.open()
            this.dialogControllerTwoArr[0].open();
          }
        })
        .margin(20)
    }.borderRadius(10)
    // 如果需要使用border属性或cornerRadius属性,请和borderRadius属性一起使用。
  }
}
@Entry
@Component
struct CustomDialogUser {
  @State textValue: string = ''

  @State inputValue: string = 'click me'
  dialogController: CustomDialogController | null = new CustomDialogController({
    builder: CustomDialogExample1({
      cancel: ()=> { this.onCancel() },
      confirm: ()=> { this.onAccept() },
      textValue: $textValue,
      inputValue: $inputValue
    }),
    cancel: this.exitApp,
    autoCancel: true,
    onWillDismiss:(dismissDialogAction: DismissDialogAction)=> {
      console.info("reason=" + JSON.stringify(dismissDialogAction.reason))
      console.log("dialog onWillDismiss")
      if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
        dismissDialogAction.dismiss()
      }
      if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
        dismissDialogAction.dismiss()
      }
    },
    alignment: DialogAlignment.Bottom,
    offset: { dx: 0, dy: -20 },
    gridCount: 4,
    customStyle: false,
    cornerRadius: 10,
  })

  // 在自定义组件即将析构销毁时将dialogController置空
  aboutToDisappear() {
    this.dialogController = null // 将dialogController置空
  }

  onCancel() {
    console.info('Callback when the first button is clicked')
  }

  onAccept() {
    console.info('Callback when the second button is clicked')
  }

  exitApp() {
    console.info('Click the callback in the blank area')
  }
  build() {
    Column() {


      Button(this.inputValue)
        .onClick(() => {
          if (this.dialogController != null) {
            this.dialogController.open()
          }
        }).backgroundColor(0x317aff)
    }.width('100%').margin({ top: 5 })
  }
}

更多关于HarmonyOS鸿蒙Next中ArkUI自定义弹框Bug的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


尊敬的开发者,您好!您的问题已受理,请您耐心等待,感谢您的理解与支持!

在HarmonyOS Next的ArkUI中,自定义弹框的常见Bug包括:弹框组件可能因布局层级或z-index设置不当而被遮挡;使用@CustomDialog装饰器时,状态管理或生命周期方法(如aboutToAppear)内的逻辑错误可能导致弹框无法正常显示或更新;动态控制弹框显示/隐藏时,状态变量绑定可能未正确触发UI刷新。部分场景下,弹框的尺寸或位置计算在屏幕旋转后可能出现异常。

这是一个已知的、在特定使用场景下会出现的问题。其根本原因在于,当 CustomDialogController 实例被存储在数组中,并通过数组索引进行调用时,this.controller 的上下文绑定可能会丢失,导致在弹框组件内部访问 this.controller 时指向了 undefined

问题核心分析: CustomDialogControlleropen()close() 方法依赖于正确的 this 上下文。当控制器对象被存入数组再取出调用时,如果处理不当,其方法执行时的作用域可能不再是原始的控制器实例本身,导致内部状态访问失败。

临时解决方案/规避方法:

  1. 传递控制器引用:在打开自定义弹框时,将当前弹框的 controller 实例作为参数传递给弹框组件。

    // 在父组件中
    let dialogController = this.dialogArray[index];
    dialogController.open({ controller: dialogController });
    
    // 在自定义弹框组件中 (@CustomDialog)
    @Param controllerRef?: CustomDialogController;
    closeDialog() {
        this.controllerRef?.close();
    }
    
  2. 使用状态管理:避免直接将控制器实例存入数组。可以存储弹框的“状态”(如是否打开),并通过唯一的标识符(如ID)来管理对应的控制器。当需要关闭时,通过事件总线或AppStorage等状态管理机制发送关闭指令,由持有控制器实例的父组件执行关闭操作。

  3. 使用Map替代数组:如果需要通过键值访问,可以考虑使用 Map 或对象来存储控制器实例,有时能更好地维护引用关系,但本质问题仍需注意上下文绑定。

根本解决: 该问题属于框架层在特定引用场景下的缺陷。建议关注后续的HarmonyOS SDK更新日志,官方会在新版本中修复此类上下文绑定丢失的问题。在修复前,请优先采用上述传递引用的方式作为稳定的临时方案。

附加说明: 确保开发环境(DevEco Studio)和SDK保持最新,并定期查看官方发布的修复列表,以获取该问题的正式补丁状态。

回到顶部