HarmonyOS鸿蒙Next中@State修饰的变量,变化后UI不更新

HarmonyOS鸿蒙Next中@State修饰的变量,变化后UI不更新 父组件 HomeCategory,子组件 QuestionItemListComp

父组件中调用子组件,同时向子组件传参,子组件的aboutToApper方法中,会根据这个参数获取请求结果。我通过log日志打印,发现请求结果是能获取到的,但是为什么UI不更新

import { PageData, QuestionItem } from "../../models"
import { promptAction } from "@kit.ArkUI"
import { http } from "../utils/Http"
import { JSON } from "@kit.ArkTS"
import { logger } from "../utils/Logger"
import json from "@ohos.util.json"
import { QuestionItemListComp2 } from "./QuestionItemListComp2"

interface QuestionType {
  name: string,
  displayNewestFlag: 1|0,
  id: number
}

interface QuestionItemType{
  text:string,
  color:Resource
}

const diffColorText:Record<number,QuestionItemType>={
  1:{text:'简单',color:$r('app.color.common_green')},
  2:{text:'简单',color:$r('app.color.common_green')},
  3:{text:'一般',color:$r('app.color.common_blue')},
  4:{text:'一般',color:$r('app.color.common_blue')},
  5:{text:'困难',color:$r('app.color.common_main_color')}
}

interface QuestionItemListParam{
  type?:string,
  keyword?:string,
  sort?:string,
  pageSize?:string,
  page?:string,
  questionBankType:string
}

@Component
struct QuestionItemListComp {

  [@State](/user/State)
  questionItemList:QuestionItem[]=[]

  @Prop
  type:string ='401'


  async getQuestionItemList(type:string,page:string,sort:string='0',pageSize:string='10',questionBankType:string='10'){
    let result = await http.request<PageData<QuestionItem>>({
      url:'test/question/list',
      params:{
        type, //QuestionItem的id
        keyword:'', //保持为空
        sort,
        pageSize,
        page,
        questionBankType
      } as QuestionItemListParam
    })
    // this.questionItemList.push(...result.rows)
    return result
  }

  async aboutToAppear(){
    const result = await this.getQuestionItemList(this.type,'1')
    // this.questionItemList.push(...result.rows)
    this.questionItemList = result.rows
    logger.info(JSON.stringify(result.rows))
  }


  //问题条框Builder
  @Builder
  QuestionItemBuilder(questionItem:QuestionItem) {
    Column({space:10}){
      Row(){
        Text(diffColorText[questionItem.difficulty].text)
        Text(questionItem.stem)
      }
      RowSplit(){
        Text(`点赞 ${questionItem.likeCount}`)
          .padding({right:10})
        Text(`浏览 ${questionItem.views}`)
          .padding({left:10,right:10})
        if (questionItem.readFlag==1){
          Text('已看过')
            .padding({left:10,right:10})
        }
      }

    }
    .margin({top:15,bottom:15})
    .alignItems(HorizontalAlign.Start)
  }

  //存放问题条框的Builder
  @Builder
  QuestionItemListBuilder(questionItems:QuestionItem[]){
    Refresh({refreshing:false}){
      List(){
        ForEach(questionItems,(item:QuestionItem,index:number)=>{
          this.QuestionItemBuilder(item)
        })
      }
      // .backgroundColor(Color.Green)
      .divider({
        strokeWidth:1,
        color:'#ffc8c8c8'
      })
      .padding({left:10,right:10})
    }
    .height('100%')
    .width('100%')

  }

  build() {
    this.QuestionItemListBuilder(this.questionItemList)
  }
}


@Component
@Preview
export struct HomeCategory {
  [@State](/user/State)
  type:string = '0'
  [@State](/user/State)
  questionTypeList: QuestionType[] = []
  [@State](/user/State)
  activeIndex: number = 0
  [@State](/user/State)
  freshFlag:boolean = false
  [@State](/user/State)
  questionItemList:QuestionItem[]=[]


  async getQuestionTypeList(){
    let result = await http.request<QuestionType[]>({
      url:'hm/question/type'
    })
    this.questionTypeList = result
  }

  //顶部问题分类builder
  @Builder
  QuestionTypeBuilder(questionType: QuestionType,index:number) {
    Row() {
      Text(questionType.name) //问题类型
        .margin({right:4})
      if (questionType.displayNewestFlag==1) { //如果是true,就显示图片new
        Image($r('app.media.ic_home_new'))
          .width(40)
      }
    }
    .padding({ left: 5, right: 5 })
    // .border({
    //   width: { bottom: this.activeIndex==questionType.id?1:0 },
    //   color: $r('app.color.black')
    // })
    .backgroundColor(this.activeIndex==index? '#ffe5e5e5' :'#00000000')
    .borderRadius('50%')

  }


  aboutToAppear(): void {
    this.getQuestionTypeList()
  }

  build() {
    Tabs() {
      ForEach(this.questionTypeList, (item: QuestionType, index: number) => {
        TabContent() {
          QuestionItemListComp({type:item.id.toString()})
        }.tabBar(this.QuestionTypeBuilder(item,index))

      })
    }
    .barMode(BarMode.Scrollable)
    .backgroundColor(Color.White)
    .barPosition(BarPosition.Start)
    .barHeight(40)
    .divider({ strokeWidth: 1, color: $r('app.color.common_gray_01') })
    // .onChange((index)=>{
    //   this.activeIndex = index
    // })
    .onTabBarClick((index:number)=>{
      this.activeIndex = index


    })

    // .height(60)


  }


}

更多关于HarmonyOS鸿蒙Next中@State修饰的变量,变化后UI不更新的实战教程也可以访问 https://www.itying.com/category-93-b0.html

5 回复

原因在于响应式只跟踪“引用地址变化”。你在子组件里更新的是数组里“同一引用对象”的内容或把服务端返回的“数据”直接赋给 questionItemList(引用未变),因此 UI 不刷新。

建议(任意其一即可触发更新):

  • 重新创建新引用再赋值:例如将结果转成全新数组(如浅拷贝或构造新模型),再赋给 questionItemList
  • 元素级替换也要保持新引用:对每个元素用“新对象”替换旧对象。
  • 若是对象模型,优先用 V2 可观察模型(@ObservedV2 + @Trace)配合组件(@ComponentV2),确保深度属性变更可被跟踪。

更多关于HarmonyOS鸿蒙Next中@State修饰的变量,变化后UI不更新的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


问题描述: 子组件对象questionItemList:QuestionItem[]=[]已经赋值了,并且有数据了,但是没有更新到UI组件,数据没有刷新。

解决方案: 从网络服务端获取到的数据源对象,需要进行new才会刷新UI

详细步骤:

async aboutToAppear() {
    const result = await this.getQuestionItemList(this.type,'1')
    result.forEach(item =>{
      this.questionItemList.push(new QuestionItem());
    })
  }

希望对您有帮助,望采纳!

  1. 直接使用ForEach替代@Builder

避免通过@Builder传递状态变量,直接在build方法中使用ForEach绑定@State变量:

@Component
struct QuestionItemListComp {
  [@State](/user/State) questionItemList: QuestionItem[] = [];

  build() {
    Refresh({ refreshing: false }) {
      List() {
        ForEach(this.questionItemList, 
          (item: QuestionItem) => {
            ListItem() {
              // 直接在此处构建UI项
              Column({ space: 10 }) {
                Row() {
                  Text(diffColorText[item.difficulty].text)
                  Text(item.stem)
                }
                // ... 其他UI内容
              }
            }
          },
          (item: QuestionItem) => item.id.toString() // 提供键值生成函数
        )
      }
    }
  }
}

2. 6.0(API20)使用@Builder响应式支持

参考 [@Builder支持状态变量刷新](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder#builder支持状态变量刷新)

从API version 20开始,开发者可以通过使用UIUtils.makeBinding()函数、Binding类和MutableBinding类实现@Builder函数中状态变量的刷新。详情请参考状态管理API文档

import { Binding, MutableBinding, UIUtils } from '@kit.ArkUI';

@ObservedV2
class ClassA {
  @Trace props: string = 'Hello';
}

[@Builder](/user/Builder)
function CustomButton(num1: Binding<number>, num2: MutableBinding<number>) {
  Row() {
    Column() {
      Text(`number1 === ${num1.value},  number2 === ${num2.value}`)
        .width(300)
        .height(40)
        .margin(10)
        .backgroundColor('#0d000000')
        .fontColor('#e6000000')
        .borderRadius(20)
        .textAlign(TextAlign.Center)

      Button(`only change number2`)
        .onClick(() => {
          num2.value += 1;
        })
    }
  }
}

[@Builder](/user/Builder)
function CustomButtonObj(obj1: MutableBinding<ClassA>) {
  Row() {
    Column() {
      Text(`props === ${obj1.value.props}`)
        .width(300)
        .height(40)
        .margin(10)
        .backgroundColor('#0d000000')
        .fontColor('#e6000000')
        .borderRadius(20)
        .textAlign(TextAlign.Center)

      Button(`change props`)
        .onClick(() => {
          obj1.value.props += 'Hi';
        })
    }
  }
}

@Entry
@ComponentV2
struct Single {
  @Local number1: number = 5;
  @Local number2: number = 12;
  @Local classA: ClassA = new ClassA();

  build() {
    Column() {
      Button(`change both number1 and number2`)
        .onClick(() => {
          this.number1 += 1;
          this.number2 += 2;
        })
      Text(`number1 === ${this.number1}`)
        .width(300)
        .height(40)
        .margin(10)
        .backgroundColor('#0d000000')
        .fontColor('#e6000000')
        .borderRadius(20)
        .textAlign(TextAlign.Center)
      Text(`number2 === ${this.number2}`)
        .width(300)
        .height(40)
        .margin(10)
        .backgroundColor('#0d000000')
        .fontColor('#e6000000')
        .borderRadius(20)
        .textAlign(TextAlign.Center)
      CustomButton(
        UIUtils.makeBinding<number>(() => this.number1),
        UIUtils.makeBinding<number>(
          () => this.number2,
          (val: number) => {
            this.number2 = val;
          })
      )
      Text(`classA.props === ${this.classA.props}`)
        .width(300)
        .height(40)
        .margin(10)
        .backgroundColor('#0d000000')
        .fontColor('#e6000000')
        .borderRadius(20)
        .textAlign(TextAlign.Center)
      CustomButtonObj(
        UIUtils.makeBinding<ClassA>(
          () => this.classA,
          (val: ClassA) => {
            this.classA = val;
          })
      )
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }
}

图片

【背景知识】

@Prop装饰的变量可以和父组件建立单向同步关系。

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

在阅读@Prop文档前,建议开发者首先了解@State的基本用法。最佳实践请参考状态管理最佳实践

【问题分析】

在V1的状态变量管理中涉及到父组件与子组件的状态变量交互的时候,需要使用@Prop或者@Link来修饰状态变量

【参考文档】

[@Link装饰器:父子双向同步-管理组件拥有的状态-状态管理(V1)-学习UI范式状态管理-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-link)

在HarmonyOS鸿蒙Next中,@State修饰的变量变化后UI不更新,可能原因包括:变量未在组件内声明、组件未使用@Entry装饰器、变量被嵌套对象包裹未使用@Observed装饰、或组件结构未正确绑定状态。检查变量是否在组件内部定义,确保使用@Entry标记根组件,复杂对象需配合@Observed@ObjectLink实现响应式更新。

回到顶部