getTarget API: Obtaining Original Objects

To obtain the original object before a proxy is added by the state management framework, you can use the getTarget API.

Before reading this topic, you are advised to review @Observed and @ObservedV2.

NOTE

The getTarget API in UIUtils is supported since API version 12.

Overview

The state management framework adds proxies to original objects of the class, Date, Map, Set, and Array types to observe attribute changes and API invoking. Proxies will change the variable types. In scenarios such as type determination and Node-API invoking, unexpected results may be generated because the variable type is not the type of the original object.

  • Import the UIUtils to use the getTarget API.

    import { UIUtils } from '@kit.ArkUI';
    
  • In state management V1, proxies are added to the following to observe changes in top-level properties or changes triggered by API calls: (1) instances of classes decorated with @Observed; (2) objects of the Class, Date, Map, Set, and Array types decorated with @State or other state variable decorators.

  • In state management V2, proxies are added to the following to observe changes triggered by API calls: objects of the Date, Map, Set, and Array types decorated with @Trace, @Local, or other state variable decorators.

The getTarget API is used to obtain the original objects of these proxy objects.

Constraints

  • Only the parameters of the object type can be passed by getTarget.

    import { UIUtils } from '@kit.ArkUI';
    let resNumber = UIUtils.getTarget(2); // Non-object input parameter. An error is reported during compilation.
    let resObject = UIUtils.getTarget(2 as Object); // Non-object type input parameter. The input value is returned directly, bypassing the compilation interception. This is an incorrect usage.
    
    import { UIUtils } from '@kit.ArkUI';
    @Observed
    class Info {
      public name: string = 'Tom';
    }
    let info: Info = new Info();
    let rawInfo: Info = UIUtils.getTarget (info); // Correct usage.
    
  • Changes to the content in the original object obtained by getTarget cannot be observed nor trigger UI re-renders.

    import { UIUtils } from '@kit.ArkUI';
    @Observed
    class Info {
      public name: string = 'Tom';
    }
    @Entry
    @Component
    struct GetTargetObject {
      @State info: Info = new Info();
    
      build() {
        Column() {
          Text(`info.name: ${this.info.name}`)
          Button('Change Proxy Object Properties')
            .onClick(() => {
              this.info.name = 'Alice'; // The Text component can be re-rendered.
            })
          Button('Change Original Object Properties')
            .onClick(() => {
              let rawInfo: Info = UIUtils.getTarget(this.info);
              rawInfo.name = 'Bob'; // The Text component cannot be re-rendered.
            })
        }
      }
    }
    

Use Scenarios

Obtaining the Original Object Before Proxy Addition in State Management V1

State management V1 adds proxies to the following objects:

  1. Instances of classes decorated with @Observed A proxy is automatically added to an instance of a class decorated with @Observed when the instance is created. In the following example, classes not decorated with @Observed are not proxied.
@Observed
class ObservedClass {
  public name: string = 'Tom';
}
class NonObservedClass {
  public name: string = 'Tom';
}
let observedClass: ObservedClass = new ObservedClass(); // Proxied.
let nonObservedClass: NonObservedClass = new NonObservedClass(); // Not proxied.
  1. Complex-type objects decorated with state variable decorators Proxies are added to objects of the Class, Map, Set, Date, or Array type decorated with @State, @Prop, or other state variable decorators. If the object is already a proxy, no new proxy is added.
@Observed
class ObservedClassOne {
  public name: string = 'Tom';
}
class NonObservedClassOne {
  public name: string = 'Tom';
}
let observedClass: ObservedClassOne = new ObservedClassOne(); // Proxied.
let nonObservedClass: NonObservedClassOne = new NonObservedClassOne(); // Not proxied.
@Entry
@Component
struct GetTargetNoChange {
  @State observedObject: ObservedClassOne = observedClass; // No new proxy is created (the object is already proxied).
  @State nonObservedObject: NonObservedClassOne = nonObservedClass; // A proxy is created.
  @State numberList: number[] = [1, 2, 3]; // A proxy is created for the Array type.
  @State sampleMap: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); // A proxy is created for the Map type.
  @State sampleSet: Set<number> = new Set([0, 1, 2, 3, 4]); // A proxy is created for the Set type.
  @State sampleDate: Date = new Date(); // A proxy is created for the Date type.

  build() {
    Column() {
      Text(`this.observedObject === observedClass: ${this.observedObject === observedClass}`) // true
      Text(`this.nonObservedObject === nonObservedClass: ${this.nonObservedObject === nonObservedClass}`) // false
    }
  }
}

Use UIUtils.getTarget to obtain the original objects before proxies are added.

import { UIUtils } from '@kit.ArkUI';
@Observed
class ObservedClass {
  public name: string = 'Tom';
}
class NonObservedClass {
  public name: string = 'Tom';
}
let observedClass: ObservedClass = new ObservedClass(); // Proxied.
let nonObservedClass: NonObservedClass = new NonObservedClass(); // Not proxied.
let globalNumberList: number[] = [1, 2, 3]; // Not proxied.
let globalSampleMap: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); // Not proxied.
let globalSampleSet: Set<number> = new Set([0, 1, 2, 3, 4]); // Not proxied.
let globalSampleDate:Date = new Date (); // Not proxied.
@Entry
@Component
struct GetTargetAgent {
  @State observedObject: ObservedClass = observedClass; // No new proxy is created (the object is already proxied).
  @State nonObservedObject: NonObservedClass = nonObservedClass; // A proxy is created.
  @State numberList: number[] = globalNumberList; // A proxy is created for the Array type.
  @State sampleMap: Map<number, string> = globalSampleMap; // A proxy is created for the Map type.
  @State sampleSet: Set<number> = globalSampleSet; // A proxy is created for the Set type.
  @State sampleDate: Date = globalSampleDate; // A proxy is created for the Date type.

  build() {
    Column() {
      Text(`this.observedObject === observedClass: ${this.observedObject ===
        observedClass}`) // true
      Text(`UIUtils.getTarget(this.nonObservedObject) === nonObservedClass: ${UIUtils.getTarget(this.nonObservedObject) ===
        nonObservedClass}`) // true
      Text(`UIUtils.getTarget(this.numberList) === globalNumberList: ${UIUtils.getTarget(this.numberList) ===
        globalNumberList}`) // true
      Text(`UIUtils.getTarget(this.sampleMap) === globalSampleMap: ${UIUtils.getTarget(this.sampleMap) ===
        globalSampleMap}`) // true
      Text(`UIUtils.getTarget(this.sampleSet) === globalSampleSet: ${UIUtils.getTarget(this.sampleSet) ===
        globalSampleSet}`) // true
      Text(`UIUtils.getTarget(this.sampleDate) === globalSampleDate: ${UIUtils.getTarget(this.sampleDate) ===
        globalSampleDate}`) // true
    }
  }
}

Obtaining the Original Object Before Proxy Addition in State Management V2

In state management V2, proxies are added to objects of the Map, Set, Date, and Array type decorated with @Trace, @Local, or other state variable decorators. Unlike in state management V1, no proxies are added to class instances in state management V2.

@ObservedV2
class ObservedClassTwo {
  @Trace public name: string = 'Tom';
}
let globalObservedObject: ObservedClassTwo = new ObservedClassTwo(); // Not proxied.
let globalNumberList: number[] = [1, 2, 3]; // Not proxied.
let globalSampleMap: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); // Not proxied.
let globalSampleSet: Set<number> = new Set([0, 1, 2, 3, 4]); // Not proxied.
let globalSampleDate:Date = new Date (); // Not proxied.
@Entry
@ComponentV2
struct GetAgentObject {
  @Local observedObject: ObservedClassTwo = globalObservedObject; // Objects in V2 are not proxied.
  @Local numberList: number[] = globalNumberList; // A proxy is created for the Array type.
  @Local sampleMap: Map<number, string> = globalSampleMap; // A proxy is created for the Map type.
  @Local sampleSet: Set<number> = globalSampleSet; // A proxy is created for the Set type.
  @Local sampleDate: Date = globalSampleDate; // A proxy is created for the Date type.

  build() {
    Column() {
      Text(`this.observedObject === globalObservedObject ${this.observedObject === globalObservedObject}`) // true
      Text(`this.numberList === globalNumberList ${this.numberList === globalNumberList}`) // false
    }
  }
}

Use UIUtils.getTarget to obtain the original objects before proxies are added.

import { UIUtils } from '@kit.ArkUI';
@ObservedV2
class ObservedClassThree {
  @Trace public name: string = 'Tom';
}
let globalObservedObject: ObservedClassThree = new ObservedClassThree(); // Not proxied.
let globalNumberList: number[] = [1, 2, 3]; // Not proxied.
let globalSampleMap: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); // Not proxied.
let globalSampleSet: Set<number> = new Set([0, 1, 2, 3, 4]); // Not proxied.
let globalSampleDate:Date = new Date (); // Not proxied.
@Entry
@ComponentV2
struct GetBeforeAgent {
  @Local observedObject: ObservedClassThree = globalObservedObject; // Objects in V2 are not proxied.
  @Local numberList: number[] = globalNumberList; // A proxy is created for the Array type.
  @Local sampleMap: Map<number, string> = globalSampleMap; // A proxy is created for the Map type.
  @Local sampleSet: Set<number> = globalSampleSet; // A proxy is created for the Set type.
  @Local sampleDate: Date = globalSampleDate; // A proxy is created for the Date type.

  build() {
    Column() {
      Text(`this.observedObject === globalObservedObject ${this.observedObject ===
        globalObservedObject}`) // true
      Text(`UIUtils.getTarget(this.numberList) === globalNumberList: ${UIUtils.getTarget(this.numberList) ===
        globalNumberList}`) // true
      Text(`UIUtils.getTarget(this.sampleMap) === globalSampleMap: ${UIUtils.getTarget(this.sampleMap) ===
        globalSampleMap}`) // true
      Text(`UIUtils.getTarget(this.sampleSet) === globalSampleSet: ${UIUtils.getTarget(this.sampleSet) ===
        globalSampleSet}`) // true
      Text(`UIUtils.getTarget(this.sampleDate) === globalSampleDate: ${UIUtils.getTarget(this.sampleDate) ===
        globalSampleDate}`) // true
    }
  }
}

In state management V2, decorators generate getter and setter methods for the decorated variables, and add the __ob_ prefix to the original variable names. For performance purposes, the getTarget API does not process the prefix added by decorators in V2. Therefore, when an instance of a class decorated with @ObservedV2 is passed to the getTarget API, the returned object remains the instance itself, and the property name decorated with @Trace still has the __ob_ prefix.

This prefix causes some Node-APIs to fail to process object properties as expected.
Example:
Affected Node-APIs include the following:

@ObservedV2
class Info {
  @Trace public name: string = 'Tom';
  @Trace public age: number = 24;
}
let info: Info = new Info(); // info instance passed in through Node-APIs.
Name Result
napi_get_property_names Returns property names with the __ob_ prefix, for example, __ob_name or __ob_age.
napi_set_property Changes values successfully using the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).
napi_get_property Obtains values using the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).
napi_has_property Returns true for both the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).
napi_delete_property Requires the __ob_ prefix for successful deletion.
napi_has_own_property Returns true for both the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).
napi_set_named_property Changes values successfully using the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).
napi_get_named_property Obtains values using the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).
napi_has_named_property Returns true for both the original name (for example, name) or the name with the __ob_ prefix (for example, __ob_name).