HarmonyOS鸿蒙Next中这里我用组件嵌套,但嵌套后一刷新就报错,我拆开就没问题,刷新就正常使用

HarmonyOS鸿蒙Next中这里我用组件嵌套,但嵌套后一刷新就报错,我拆开就没问题,刷新就正常使用

@Component
struct HealthPage {
  @State isRefreshing: boolean = false;
  @State bannerList: string[] = [];

  onRefreshStatusChange() {
    if (this.isRefreshing) {
      this.loadData().then(() => {
        this.isRefreshing = false;
      });
    }
  }

  async loadData() {
    console.info('加载数据...');
    await new Promise<void>((resolve) => setTimeout(resolve, 1500));
    this.bannerList = [
      'https://picsum.photos/seed/1/300/160',
      'https://picsum.photos/seed/2/300/160',
      'https://picsum.photos/seed/3/300/160',
      'https://picsum.photos/seed/4/300/160',
      'https://picsum.photos/seed/5/300/160',
      'https://picsum.photos/seed/6/300/160',
      'https://picsum.photos/seed/7/300/160',
    ];
  }

  build() {
    TitleWithBody({
      title: $r('app.string.health'),
      content: this.contentBuilder // 使用@Builder方法
    })
  }

  @Builder
  contentBuilder() {
    Refresh({ refreshing: $$this.isRefreshing }) {
      Scroll() {
        Column({ space: 12 }) {
          Text('下拉刷新示例').fontSize(22).margin(10)
          ForEach(this.bannerList, (img: string) {
            Image(img)
              .width(300)
              .height(160)
          })
        }
      }
      .friction(5)
    }
    .onRefreshing(() => this.onRefreshStatusChange())
    .width('100%')
    .height('100%')
  }
}

@Component
export struct SimpleTitleBar {
  private title: ResourceStr = "";

  constructor(title?: ResourceStr) {
    super(); // ✅ 必须在构造函数第一行
    if (title) {
      this.title = title;
    }
  }

  build() {
    Row() {
      // 直接在UI中处理资源类型
      Text(
        // 使用三元表达式作为UI描述的一部分
        typeof this.title === 'string'
          ? this.title
          : (this.title as Resource)
      )
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.Black)
        .layoutWeight(1)
        .textAlign(TextAlign.Center)
    }
    .width('100%')
    .height(50)
    .backgroundColor('#f0f0f0')
    .padding(10)
  }
}

@Component
export struct TitleWithBody {
  private title: ResourceStr = "";
  // 使用更明确的类型定义
  @BuilderParam content: () => void;

  constructor(params?: {
    title?: ResourceStr,
    content?: () => void
  }) {
    super(); // ✅ 必须在构造函数第一行
    this.title = params?.title ?? "";

    // 处理body参数
    if (params?.content) {
      this.content = params.content;
    } else {
      this.content = this.defaultContent;
    }
  }

  // 默认内容 - 返回一个有效的UI元素
  @Builder
  defaultContent() {
    Column() {
      // 空内容,但返回有效的UI结构
    }
  }

  build() {
    Column() {
      // 标题区域 - 直接使用SimpleTitleBar组件
      SimpleTitleBar({ title: this.title })

      // 内容区域 - 安全调用content构建器
      Column() {
        this.content() // 调用传入的内容构建器
      }
      .layoutWeight(1)
      .width('100%')
    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.White)
  }
}

更多关于HarmonyOS鸿蒙Next中这里我用组件嵌套,但嵌套后一刷新就报错,我拆开就没问题,刷新就正常使用的实战教程也可以访问 https://www.itying.com/category-93-b0.html

9 回复
  1. 报错的原因是状态变量的使用问题,因为@Builder已经作为一个@Builder传到另外一个组件使用了 它的回调什么的都不是在当前组件要去处理的

  2. 报错的信息是:is not callable,你可以try一下

  3. 我修改了一下代码,你可以使用回调来处理你的刷新情况,可以在我的思路上在改改

  4. 修改后的代码:

@Component
struct HealthPage {
  @State isRefreshing: boolean = false;
  @State bannerList: string[] = [];

  onRefreshStatusChange() {
    if (this.isRefreshing) {
      this.loadData().then(() => {
        this.isRefreshing = false;
      });
    }
  }

  async loadData() {
    console.info('加载数据...');
    await new Promise<void>((resolve) => setTimeout(resolve, 1500));
    this.bannerList = [
      'https://picsum.photos/seed/1/300/160',
      'https://picsum.photos/seed/2/300/160',
      'https://picsum.photos/seed/3/300/160',
      'https://picsum.photos/seed/4/300/160',
      'https://picsum.photos/seed/5/300/160',
      'https://picsum.photos/seed/6/300/160',
      'https://picsum.photos/seed/7/300/160',
    ];
  }

  build() {
    TitleWithBody({
      title: $r('app.string.app_name'),
      content: this.contentBuilder, // 使用[@Builder](/user/Builder)方法,
      isRefreshing: this.isRefreshing,
      callBack: () => {
        AlertDialog.show({ message: JSON.stringify('callBack', null, 2) })
        this.onRefreshStatusChange
      }
    })
  }

  [@Builder](/user/Builder)
  contentBuilder(isRefreshing: boolean, callBack: Callback<void>) {
    Refresh({ refreshing: isRefreshing }) {
      Scroll() {
        Column({ space: 12 }) {
          Text('下拉刷新示例').fontSize(22).margin(10)
          ForEach(this.bannerList, (img: string) => {
            Image(img)
              .width(300)
              .height(160)
          })
        }
      }
      .friction(5)
    }
    .onRefreshing(() => {
      callBack()
    })
    .width('100%')
    .height('100%')
  }
}

@Component
export struct SimpleTitleBar {
  @Prop title: ResourceStr = "";

  constructor(title?: ResourceStr) {
    super(); // ✅ 必须在构造函数第一行
    if (title) {
      this.title = title;
    }
  }

  build() {
    Row() {
      // 直接在UI中处理资源类型
      Text(
        // 使用三元表达式作为UI描述的一部分
        typeof this.title === 'string'
          ? this.title
          : (this.title as Resource)
      )
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .fontColor(Color.Black)
      .layoutWeight(1)
      .textAlign(TextAlign.Center)
    }
    .width('100%')
    .height(50)
    .backgroundColor('#f0f0f0')
    .padding(10)
  }
}

@Component
export struct TitleWithBody {
  // 使用更明确的类型定义
  @BuilderParam content: (a: boolean, callBack: Callback<void>) => void;
  @Prop title: ResourceStr = "";
  @Link isRefreshing: boolean

  constructor(params?: {
    title?: ResourceStr,
    content?: () => void
  }) {
    super(); // ✅ 必须在构造函数第一行
    this.title = params?.title ?? "";

    // 处理body参数
    if (params?.content) {
      this.content = params.content;
    } else {
      this.content = this.defaultContent;
    }
  }

  callBack: Callback<void> = () => {
  }

  // 默认内容 - 返回一个有效的UI元素
  [@Builder](/user/Builder)
  defaultContent() {
    Column() {
      // 空内容,但返回有效的UI结构
    }
  }

  build() {
    Column() {
      // 标题区域 - 直接使用SimpleTitleBar组件
      SimpleTitleBar({ title: this.title })

      // 内容区域 - 安全调用content构建器
      Column() {
        this.content(this.isRefreshing, this.callBack) // 调用传入的内容构建器
      }
      .layoutWeight(1)
      .width('100%')
    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.White)
  }
}

更多关于HarmonyOS鸿蒙Next中这里我用组件嵌套,但嵌套后一刷新就报错,我拆开就没问题,刷新就正常使用的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


我把他改成插槽模式就行了,我现在试试看你这里的方法,看看能不能用,因为我一些组件可能会传多个,插槽只能有一个,我还不知道怎么通过参数的形式去传递外部的组件

而且这个刷新方法我想做成公共的,这样只需要通过判断传入方法就可以决定是否开启刷新功能,这样我可以避免刷新滚动这些组件重复套用,我暂时实现了,但是是通过插槽实现的,

插槽也可以定义多个的,使用那个插槽再通过其他的参数来配合使用,

大佬有相关demo地址吗,我记得官方有一个demo也是只支持一个插槽,剩下的也是通过参数传递,

【背景知识】 在自定义组件中使用this,通常是在回调事件中,此时this指向自定义组件本身。 ArkTS的this指向调用实例方法的对象或者正在构造的对象。在问题中,this.onRefreshStatusChange()的是由子组件调用,指向子组件。但子组件并没有onRefreshStatusChange()方法,所以报错。

js等其他语言中,可以使用bindapplycall等方法,使this不再指向调用实例的方法,能使用bind解决问题。但ArkTS不支持以上方法,会触发warning arkts-no-func-apply-bind-call。建议使用箭头函数替代bind(this)

【解决方案】 方案一:使用箭头函数替代bind,将this指向锁定为父组件。 示例代码如下:

build() {
  Column() {
    TitleWithBody({
      title: $r('app.string.app_name'),
      // content: this.contentBuilder // 使用@Builder方法
      content: (): void => this.contentBuilder() // 使用箭头函数确定this指向
    })
  }
}

方案二:使用@LocalBuilder装饰器,将组件与父组件绑定。

当开发者使用局部@Builder进行引用数据传递时,需要考虑组件的父子关系。然而在使用.bind(this)的方式更改函数调用上下文后,会出现组件的父子关系与状态管理的父子关系不一致的问题。为了解决这一问题,引入@LocalBuilder装饰器。@LocalBuilder拥有和局部@Builder相同的功能,且比局部@Builder能够更好的确定组件的父子关系和状态管理的父子关系。

由于@LocalBuilder会保持组件内部的状态封闭,使用$$语法进行双向绑定会引入外部状态变化,从而破坏组件的父子关系一致性,干扰@LocalBuilder内部的状态管理,所以$$语法会被限制,导致无法生效,产生报错。所以需要修改原有的$$this.isRefreshing

此方案同样能使this指向父组件,若子组件需要传递contentBuilder,可以直接传递,而方案一则需要继续套箭头函数。

@LocalBuilder
contentBuilder() {
  Refresh({ refreshing: this.isRefreshing }) {
    Scroll() {
      Column({ space: 12 }) {
        Text('下拉刷新示例').fontSize(22).margin(10)
        ForEach(this.bannerList, (img: string) => {
          Image(img)
            .width(300)
            .height(160)
        })
      }
    }
    .friction(5)
  }
  .onRefreshing(() => {
    this.isRefreshing = true
    this.onRefreshStatusChange()
  })
  .width('100%')
  .height('100%')
}

【常见FAQ】 Q:什么场景容易出现this指向不明确的问题? A:函数中若使用了this,需要特别注意上下文是否会发生变化。一般在定义函数的类中,this的配置是正常的。但是若函数的上下文发生改变,比如其他函数调用,出现在箭头函数中。箭头函数的this指向调用者,回调常用箭头函数实现。 foo(funcName)的形式传递使用this的函数比较危险,this指向不能确定。若通过foo(() => {funcName()})箭头函数的形式传递,则this的指向确定,为调用foo()的函数而非foo中。

猜测是不是ColumnRefresh嵌套引发布局冲突?你试一下调整组Refresh为顶级容器行不行?

// TitleWithBody.build()
Column() {
  SimpleTitleBar({ title: this.title })
  Refresh({ refreshing: $$this.refreshState }) { // 将Refresh移至此处
    Scroll() {
      this.content()
    }
  }
}

我把他改成插槽模式就行了,用参数的方式就报错,暂时还不清楚问题,

在HarmonyOS Next中,组件嵌套刷新报错可能是由组件的生命周期管理或状态同步问题导致。检查父组件和子组件的@State/@Link装饰器使用是否正确,确保嵌套组件之间的数据绑定关系清晰。若使用@Provide/@Consume,需确认作用域范围是否覆盖嵌套层级。同时验证组件刷新时是否触发了重复渲染或无效状态更新。建议使用DevEco Studio的调试工具检查组件树结构和状态变化。

从代码来看,问题可能出在Refresh组件的嵌套使用方式上。在HarmonyOS Next中,Refresh组件需要正确管理刷新状态和布局层级。建议检查以下几点:

  1. 确保Refresh组件直接包含Scroll组件,中间不要有其他容器组件

  2. 检查contentBuilder中的$$this引用是否正确绑定到当前组件实例

  3. 尝试将Refresh的onRefreshing回调改为箭头函数直接绑定:

    .onRefreshing(() => {
      this.isRefreshing = true;
      this.loadData().then(() => {
        this.isRefreshing = false;
      });
    })
    
  4. 确认TitleWithBody组件的高度设置是否正确,可能需要明确指定高度值而非百分比

另外,可以尝试在Refresh外层添加固定高度的Column容器,确保布局尺寸计算正确。如果仍有问题,建议提供具体的错误日志信息以便进一步分析。

回到顶部