HarmonyOS 鸿蒙Next中@Consumer重复初始化ViewModel的问题

HarmonyOS 鸿蒙Next中@Consumer重复初始化ViewModel的问题 父子Component共用ObservedV2的情况下,通过@Provider@Consumer进行共享

import hilog from '@ohos.hilog';

const DOMAIN = 0x00000
const TAG = 'DEMO'

@ObservedV2
class MyViewModel {
  private static counter = 0
  @Trace bar: string = 'foo'

  constructor() {
    hilog.info(DOMAIN, TAG, `constructor ${MyViewModel.counter}`)
    MyViewModel.counter++
  }
}

@ComponentV2
struct Component1 {
  [@Consumer](/user/Consumer)('myViewModel') myViewModel: MyViewModel = new MyViewModel()

  build() {
    Text(this.myViewModel.bar)
  }
}

@Entry
@ComponentV2
struct Index {
  [@Provider](/user/Provider)('myViewModel') myViewModel: MyViewModel = new MyViewModel()

  build() {
    Column() {
      Text(this.myViewModel.bar)
      Component1()
    }
  }

  aboutToAppear() {
    this.myViewModel.bar = 'foo2'
  }
}

期望:共用一个viewModel对象,减少资源浪费
实际:子Component也初始化了一个viewModel对象

log:

08-28 13:14:45.412   12196-12196   A00000/com.xxx.playground/DEMO  pid-12196             I     constructor 0

08-28 13:14:45.414   12196-12196   A00000/com.xxx.playground/DEMO  pid-12196             I     constructor 1


更多关于HarmonyOS 鸿蒙Next中@Consumer重复初始化ViewModel的问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

14 回复

@Consumer装饰的属性若在声明时直接初始化赋值(如= new MyViewModel()),会绕过Provider的共享机制,导致重复创建实例。

解决方案:修改子组件Component1的代码,移除显式初始化逻辑:

@ComponentV2
struct Component1 {
  [@Consumer](/user/Consumer)('myViewModel') myViewModel: MyViewModel; // 移除初始化
  build() {
    Text(this.myViewModel.bar)
  }
}

更多关于HarmonyOS 鸿蒙Next中@Consumer重复初始化ViewModel的问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


新的语法检查更严格了,会报错 Property ‘myViewModel’ has no initializer and is not definitely assigned in the constructor. <ArkTSCheck>,

@ComponentV2
struct Component1 {
  @Consumer('myViewModel') myViewModel?: MyViewModel|undefined

  build() {
    Text(this.myViewModel?.bar)
  }
}

嗯嗯, 这个也是我试下来的方法,贴在3楼了,

@ComponentV2
struct Component1 {
  @Consumer('myViewModel') myViewModel?: MyViewModel
  build() {
    Text(this.myViewModel?.bar)
  }
}

目前我能想到的是在子组件中将viewModel设置为可空, 但是使用中还需要加?符

当使用 @Provider@Consumer 实现父子组件共享 @ObservedV2 修饰的 ViewModel 时,子组件重复初始化 ViewModel 的根本原因在于 @Consumer 的初始化方式不符合规范

将子组件中的 @Consumer 变量声明改为 仅类型声明,避免主动初始化:

@ComponentV2
struct Component1 {
  [@Consumer](/user/Consumer)('myViewModel') myViewModel: MyViewModel // 删除 = new MyViewModel()

  build() {
    Text(this.myViewModel.bar)
  }
}

确保父组件和子组件中 @Provider@Consumer 的 标识符一致(示例中均为 ‘myViewModel’),且 ViewModel 类型完全匹配。

// 单例模式示例
[@ObservedV2](/user/ObservedV2)
class MyViewModel {
  private static instance: MyViewModel;
  @Trace bar: string = 'foo';

  private constructor() {}

  static getInstance(): MyViewModel {
    if (!MyViewModel.instance) {
      MyViewModel.instance = new MyViewModel();
    }
    return MyViewModel.instance;
  }
}

// 父组件中使用单例
@Entry
@ComponentV2
struct Index {
  [@Provider](/user/Provider)('myViewModel') myViewModel: MyViewModel = MyViewModel.getInstance()
  // ...
}

方案1会报错 Property ‘myViewModel’ has no initializer and is not definitely assigned in the constructor. <ArkTSCheck>

方案2如果采用单例的话,那也没必要使用@Provider@Consumer了。 另外私有构造函数也不支持使用PersistenceV2.globalConnect进行持久化,

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

方案1只适用在v1,v2中是必须初始化的,

2个对象是对的 consumer里初始化的是用来做默认值的

cke_5780.png

当找不到的@Provider的时候使用本地默认值, 我的case里应该可以找到, 为啥还执行了本地默认值的构造函数? 不是浪费性能么,

学习一下单例模式,什么是懒汉式,什么是饿汉式,是一个原理。Component1 在内存初始化的时候,会初始化他的属性(成员变量),他走到 @Consumer(‘myViewModel’) myViewModel: MyViewModel = new MyViewModel()的时候就会创建对象,这是不可避免的。 退一步说,就算创建了2个对象,但是后续@Provider@Consumer只指向了一个对象,另一个对象占的内存空间迟早也会被垃圾回收,

在HarmonyOS鸿蒙Next中,@Consumer装饰器用于监听状态变化。当多个@Consumer同时依赖同一个ViewModel时,若未正确管理状态依赖,可能导致ViewModel被重复初始化。这通常是由于状态管理逻辑未隔离或作用域冲突引起的。需检查@State@Link的数据流设计,确保ViewModel实例在组件树中保持单例。可通过合理使用@Provide@Consume装饰器或调整组件结构来避免重复创建。

在HarmonyOS Next中,@Consumer装饰器用于接收由@Provider提供的共享状态对象,但代码中@Consumer字段被赋予了默认值new MyViewModel(),这会导致重复初始化。正确的做法是移除默认赋值,仅通过装饰器注入:

@ComponentV2
struct Component1 {
  @Consumer('myViewModel') myViewModel: MyViewModel  // 移除 = new MyViewModel()

  build() {
    Text(this.myViewModel.bar)
  }
}

这样,子组件会直接使用父组件通过@Provider提供的实例,避免重复创建。当前log显示两次构造函数调用(counter 0和1),正是因为子组件独立初始化了实例。移除默认值后,父子组件将共享同一实例,资源开销减少。

回到顶部