HarmonyOS鸿蒙Next中@State变量同步赋值给工具类的变量后,在工具类中更新该变量会丢失响应式

HarmonyOS鸿蒙Next中@State变量同步赋值给工具类的变量后,在工具类中更新该变量会丢失响应式

问题现象

写播放类的时候遇到的,想在播放工具类中通过传递过来的 UI 的变量更新播放状态之类的来控制 UI 展示:

在UI中声明的@State

  1. 如果在 aboutToAppear 中赋值传递给工具类的话,后续在工具类中的更新触发 UI 更新;
  2. 如果是通过点击事件的话,赋值传递给工具类的话,后续在工具类中的更新不会触发 UI 更新,需要触发 UI 组件的更新方法才会更新 UI;

我看了一下ArkUI Inspector,两个变量都跟UI组件绑定成功,各自对象的hashCode与传递给工具类的都相同。

想问一下为什么会这样,要怎么解决?

代码信息

class TestData {
  idx: number = 0;
  a: string = '';
  b: string = '';
  c: string = '';
}

class TestUtil {
  private data: TestData = new TestData();

  setData(data: TestData) {
    this.data = data;
  }

  updateDataA() {
    // 这里模拟业务中的更新场景
    this.data.idx += 1;
  }
}

@Component
export struct TestView {
  [@State](/user/State) data: TestData = new TestData();
  [@State](/user/State) data2: TestData = new TestData();
  private testUtil: TestUtil = new TestUtil();
  private testUtil2: TestUtil = new TestUtil();

  aboutToAppear(): void {
    this.data.idx = 1;
    this.data.a = '标题1';
    this.data.b = '测试1';
    this.data.c = '测试c';

    this.testUtil.setData(this.data);

    this.data2.idx = 1;
    this.data2.a = '标题2';
    this.data2.b = '测试2';
    this.data2.c = '测试2c';
  }

  build() {
    Column({ space: 10 }) {
      this.TestViewA()

      this.TestViewB()
    }
    .backgroundColor(Color.White)
    .width('100%')
    .height('100%')
  }

  @Builder
  TestViewA() {
    Row({ space: 10 }) {
      Text('idx:' + this.data.idx)
      Button('+1').onClick(() => {
        this.testUtil.updateDataA();
      })
    }
  }

  @Builder
  TestViewB() {
    Row({ space: 10 }) {
      Text('idx:' + this.data2.idx)
      Button('setData').onClick(() => {
        this.testUtil2.setData(this.data2)
      })
      Button('+1').onClick(() => {
        this.testUtil2.updateDataA();
      })
      Button('local +1').onClick(() => {
        this.data2.idx++;
      })
    }

  }
}

版本信息

手机系统版本:meta60,6.0.0.328;

DevEco Studio:6.0.2.642;

复现步骤

按顺序点按钮即可


更多关于HarmonyOS鸿蒙Next中@State变量同步赋值给工具类的变量后,在工具类中更新该变量会丢失响应式的实战教程也可以访问 https://www.itying.com/category-93-b0.html

16 回复

开发者您好,

在build方法内,当@State装饰的变量是Object类型,且通过a.b(this.object)形式调用时,b方法内传入的是this.object的原始对象,修改其属性,无法触发UI刷新。详情参考使用a.b(this.object)形式调用,不会触发UI刷新

由于您代码中this.testUtil2.setData(this.data2)是在build内调用的,会导致上述现象。建议通过官网提供的正例:临时变量方式修改。或者不在build方法内调用,在build外调用即可。

更多关于HarmonyOS鸿蒙Next中@State变量同步赋值给工具类的变量后,在工具类中更新该变量会丢失响应式的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


@Builder
  TestViewB() {
    Row({ space: 10 }) {
      Text('idx:' + this.data2.idx)
      Button('setData').onClick(() => {
        const data2 = this.data2;
        this.testUtil2.setData(data2)
      })
      Button('+1').onClick(() => {
        this.testUtil2.updateDataA();
      })
      Button('local +1').onClick(() => {
        this.data2.idx++;
      })
    }
  }

多谢👍,改成这样就可以了

不错

不错

cke_491.png

这里要是改成箭头函数,“后续在工具类中的更新触发 UI 更新”就也不生效了。

不过,实际上即使在aboutToAppear写进工具类内也应该不生效才对的。

建议提bug,或者工单吧。

我也没太搞明白什么意思,AI的解释大概意思是不在aboutToAppear中调用setData,testUtil初始化的时候就会默认设置一个data,然后再调用setData的时候直接把新的data的引用赋值给testUtil的data而不是对象本身,UI变化不会深度关联对象导致UI不刷新,使用Observed配合ObjectLink可以实现。下面是修改后的代码,希望对你有帮助

@Observed
class TestData {
  idx: number = 0;
  a: string = '';
  b: string = '';
  c: string = '';
}

class TestUtil {
  private data: TestData = new TestData();

  setData(data: TestData) {
    this.data = data;
  }

  updateDataA() {
    // 这里模拟业务中的更新场景
    this.data.idx += 1;
  }
}
@Component
struct MyComponent {
  @ObjectLink data2: TestData;  // 自动深度观察

  build() {
    Button("+1")
      .onClick(() => this.data2.idx += 1) // 直接修改自动更新
  }
}




@Entry
export struct MvMM {
  @State data: TestData = new TestData();
  @State data2: TestData = new TestData();
  private testUtil: TestUtil = new TestUtil();
  private testUtil2: TestUtil = new TestUtil();

  aboutToAppear(): void {
    this.data.idx = 1;
    this.data.a = '标题1';
    this.data.b = '测试1';
    this.data.c = '测试c';

    this.testUtil.setData(this.data);

    this.data2.idx = 1;
    this.data2.a = '标题2';
    this.data2.b = '测试2';
    this.data2.c = '测试2c';
  }

  build() {
    Column({ space: 10 }) {
      this.TestViewA()
      this.TestViewB()
    }
    .backgroundColor(Color.White)
    .width('100%')
    .height('100%')
  }

  @Builder
  TestViewA() {
    Row({ space: 10 }) {
      Text('idx:' + this.data.idx)
      Button('+1').onClick(() => {
        this.testUtil.updateDataA();
      })
    }
  }

  @Builder
  TestViewB() {
    Row({ space: 10 }) {
      Text('idx:' + this.data2.idx)
      Button('setData').onClick(() => {
        this.testUtil2.setData(this.data2)
      })
      // Button('+1').onClick(() => {
      //   this.testUtil2.updateDataA();
      // })
      MyComponent({ data2:this.data2 })
      Button('local +1').onClick(() => {
        this.data2.idx++;
      })
    }

  }
}

应该不是深层嵌套结构和引用的问题,感觉像是组件在初始化时给变量创建绑定了什么东西,但在之后赋值的好像没法用到那套流程触发响应式😂

嗯,是这个样子的,有点像C++中的只是赋值了一个指针地址,并没有把对象赋值过去

在HarmonyOS鸿蒙Next中,@State变量是组件内响应式数据,直接赋值给工具类的普通变量会丢失响应式绑定。工具类中更新的是普通变量副本,不会触发UI重新渲染。若需在工具类中保持响应式,可使用@Observed@ObjectLink装饰器,或将工具类变量设计为引用类型(如对象属性),通过状态管理方案(如AppStorage)共享状态。

这是一个关于响应式数据传递和引用丢失的典型问题。关键在于理解 @State 变量的响应式机制在赋值时的行为差异。

核心原因: @State 装饰的变量在 aboutToAppear 生命周期和事件回调中,其响应式跟踪的“上下文”或“快照”状态不同。直接传递对象引用时,工具类持有的是对象本身,但可能丢失了 ArkUI 框架用于触发 UI 更新的那个特定的“响应式包装层”或“观察上下文”。

具体分析:

  1. aboutToAppear 中赋值可行:在组件初始构建阶段,@State 变量 data 的响应式绑定已经建立。此时将其引用传递给 testUtil,工具类后续对对象属性的修改,框架可能仍能追踪到变化并触发 UI 更新(这依赖于框架内部实现,可能是一种隐式优化或特定生命周期内的行为)。

  2. 点击事件中赋值后更新无效:在事件回调中,@State 变量 data2 的引用被传递给 testUtil2。然而,事件回调中对 @State 变量的直接引用,可能并不自动携带用于触发 UI 重新渲染的完整响应式触发器。工具类 testUtil2 修改 data2 的属性时,框架的响应式系统没有检测到这个变更,因为变更发生在工具类内部,而不是在 ArkUI 管理的响应式上下文中(例如,不是通过修改 this.data2.idx 本身)。

解决方案:

不要直接传递 @State 变量对象本身给工具类。应该传递一个能够通知 UI 更新的“控制器”或“回调方法”。

修改建议:

  1. 在工具类中定义回调接口

    class TestUtil {
      private data: TestData | null = null;
      private onDataUpdate: (() => void) | null = null; // 新增:更新回调
    
      setData(data: TestData, onUpdate: () => void) { // 修改:传入回调
        this.data = data;
        this.onDataUpdate = onUpdate;
      }
    
      updateDataA() {
        if (this.data) {
          this.data.idx += 1;
          this.onDataUpdate?.(); // 修改:通知UI更新
        }
      }
    }
    
  2. 在UI组件中传递更新函数

    // 在 aboutToAppear 中
    this.testUtil.setData(this.data, () => {
      // 空函数,因为aboutToAppear中可能不需要,或保留
    });
    
    // 在点击事件中
    Button('setData').onClick(() => {
      this.testUtil2.setData(this.data2, () => {
        this.data2 = Object.assign({}, this.data2); // 触发UI更新
      });
    })
    
  3. 或者,在工具类更新后,手动触发UI变更(在事件回调场景中):

    Button('+1').onClick(() => {
      this.testUtil2.updateDataA();
      // 手动通知框架数据已变
      this.data2 = Object.assign({}, this.data2); // 创建一个新对象引用
    })
    

总结: 直接传递 @State 对象引用给外部工具类,尤其是在事件回调中,会绕过 ArkUI 的响应式跟踪系统。最佳实践是让工具类通过回调函数通知组件,由组件负责触发响应式更新(例如重新赋值)。这样可以确保数据流清晰,且符合框架的响应式设计模式。

回到顶部