Sendable对象改造实践
==============

#### 简介

本文介绍[TurboTransJSON](https://gitcode.com/openharmony-sig/turbo_trans)工具中@hadss/turbo-trans-json（下文简称**turbotransJSON**）与@hadss/turbo-trans-protobuf（下文简称**turbotransProtobuf**）在ArkTS中操作Sendable对象的典型用法：

*   使用**turbotransJSON**将JSON字符串反序列化为对象，并转换为Sendable对象在并发实例间传递，适用于将普通对象或JSON字符串转换为Sendable对象，详细可参考[使用turbotransJSON序列化/反序列化并生成Sendable对象](#使用turbotransjson序列化反序列化并生成sendable对象)。
*   使用**turbotransProtobuf**通过.proto生成@Sendable的消息类，并进行编码/解码，详细可参考[使用turbotransProtobuf生成Sendable对象并编解码](#使用turbotransprotobuf生成sendable对象并编解码)。
*   使用turbotransJSON或turbotransProtobuf转换生成Sendable对象后，如果还需要绑定UI进行组件刷新的话，可以阅读[使用makeObserved将Sendable对象转换为可观察对象](#使用makeobserved将sendable对象转换为可观察对象)章节。

#### 使用TurboTrans三方库操作Sendable对象

#### \[h2\]使用turbotransJSON序列化/反序列化并生成Sendable对象

1.  环境配置
    
    引入TurboTransJSONPlugin和TurboTransJSON三方库。
    
      "dependencies": {
    // ...
        "@hadss/turbo-trans-json-plugin": "latest",
      },
    
    在工程目录或模块下使用ohpm方式安装三方库。
    
    ohpm install @hadss/turbo-trans-core @hadss/turbo-trans-json
    
    插件生效还需在工程根目录hvigorfile.ts添加相关插件配置。
    
    import { turboTransJsonPlugin } from '@hadss/turbo-trans-json-plugin';
    import { hvigor } from '@ohos/hvigor';
    import { appTasks } from '@ohos/hvigor-ohos-plugin';
    
    export default {
      system: appTasks, /\* Built-in plugin of Hvigor. It cannot be modified. \*/
      plugins: \[
        turboTransJsonPlugin(hvigor, {
          ignoreModuleNames: \['TurboTransCore' , 'TurboTransJSON',  'PerformanceBaseline','TurboTransProtobuf'\], // 忽略的模块
          scanDir: \['src/main/ets'\], // 扫描目录
          deserializationMode: 'performance', // 反序列化模式
        }),
      \]       /\* Custom plugin to extend the functionality of Hvigor. \*/
    }
    
2.  定义可序列化模型。
    
    通过@Serializable装饰器标记需序列化的类，添加generateSendable: true属性，并利用@SerialName()完成属性的定制化配置，其中：
    
    *   @Serializable({ generateSendable: true })：表示需要生成与该模型对应的Sendable类型与转换方法。
        
    *   @SerialName({ name: 'xxx' })：将类属性与JSON字段名绑定。
        
        import { Serializable, SerialName } from '@hadss/turbo-trans-core';
        // ...
        
        @Serializable({ generateSendable: true})
        export class Layout {
          @SerialName({ name: 'type'})
          public type: string = '';
          @SerialName({ name: 'arr'})
          public arr: number\[\] = \[\];
        }
        
        @Sendable
        export class LayoutS {
          public type: string = '';
          public arr: collections.Array<number> = new collections.Array();
        }
        
3.  从JSON字符串反序列化为普通对象（TJSON.fromString）。
    
    使用TJSON.fromString<Model>(jsonStr, Model)从JSON字符串反序列化为普通对象，可用ArkTSUtils.isSendable(obj)进行校验。工程示例演示了两种输入来源，并在反序列化后继续将普通对象转换为Sendable对象，形成“JSON字符串 -> 普通对象 -> Sendable对象”的前半段链路。
    
    *   testJSON1方法是将普通对象通过JSON.stringify()序列化为JSON字符串后，再将JSON字符串反序列化为普通对象，最后再转换为Sendable对象的过程。
        
    *   testJSON2方法是将手写的JSON字符串反序列化为普通对象，然后再转换为Sendable对象的过程。
        
        import { TJSON } from '@hadss/turbo-trans-json';
        import { Layout, LayoutS } from 'entry/ets/turbotrans\_JSON/layout';
        import type { ITSerializable } from '@hadss/turbo-trans-json';
        import { ArkTSUtils } from '@kit.ArkTS';
        
        export function testJSON1(): LayoutS {
          let obj = new Layout();
          obj.type = 'Test';
          obj.arr = \[3, 4\];
          let str = JSON.stringify(obj);
          let layoutNormal = TJSON.fromString<Layout>(str, Layout);
          console.info('111 layout arr: ' + layoutNormal.arr);
          let layoutSendable = (layoutNormal as object as ITSerializable).toSendable();
          if (ArkTSUtils.isSendable(layoutSendable)) {
            console.info('expect layout from JSON string is Sendable');
          } else {
            console.error('test occur error');
          }
          let layoutS = layoutSendable as LayoutS;
          return layoutS;
        }
        
        export function testJSON2(): LayoutS {
          let layoutStr = '{"type":"Text","arr":\[3,4\]}';
          let layoutNormal = TJSON.fromString<Layout>(layoutStr, Layout);
          console.info('222 layout arr: ' + layoutNormal.arr);
          let layoutSendable = (layoutNormal as object as ITSerializable).toSendable();
          if (ArkTSUtils.isSendable(layoutSendable)) {
            console.info('expect layout from simple string is Sendable');
          } else {
            console.error('test occur error');
          }
          let layoutS = layoutSendable as LayoutS;
          return layoutS;
        }
        
4.  将普通对象转换为Sendable对象（toSendable）。
    
    当generateSendable: true生效后，构建产物中会生成toSendable()，将普通对象转换为Sendable对象。其关键点在于会对普通对象做必要的容器转换，例如把number\[\]转换为collections.Array<number>。以下代码为编译时工具自动生成的，文件位于entry/src/generated/ets目录下。
    
    import { collections } from '@kit.ArkTS';
    
    toSendable(): SendableLayout {
      const sendable = new SendableLayout();
    
      // 非构造函数属性赋值
      sendable.type = this.type;
      sendable.arr = new collections.Array(...this.arr);
    
      return sendable;
    }
    
    同时，生成的Sendable类型通常会提供toOrigin()，用于在需要继续按普通对象处理、复用原有逻辑或重新序列化时，将Sendable对象还原为普通对象。以下代码为编译时工具自动生成的，文件位于entry/src/generated/ets目录下。
    
    @Sendable
    export class SendableLayout implements lang.ISendable {
      public type: string = '';
      public arr: collections.Array<number> = new collections.Array();
    
      toOrigin(): Layout {
        const origin = new Layout();
        origin.type = this.type;
        origin.arr = new Array(...this.arr);
        return origin;
      }
    }
    
    JSON路径就形成了完整闭环：
    
    JSON字符串 -> TJSON.fromString() -> 普通对象 -> toSendable() -> Sendable对象 -> toOrigin() -> 普通对象。
    

#### \[h2\]使用turbotransProtobuf生成Sendable对象并编解码

1.  环境配置
    
    引入TurboTransProtobufPlugin和TurboTransProtobuf三方库。
    
    "dependencies": {
        // ...
        "@hadss/turbo-trans-protobuf-plugin": "latest"
    },
    
    在工程目录或者模块下使用ohpm方式安装三方库。
    
    ohpm install @hadss/turbo-trans-protobuf
    
    插件生效还需要在工程根目录的hvigorfile.ts文件中添加相关插件配置。
    
    import { turboTransProtobufPlugin } from '@hadss/turbo-trans-protobuf-plugin';
    import { hvigor } from '@ohos/hvigor';
    import { appTasks } from '@ohos/hvigor-ohos-plugin';
    
    export default {
      system: appTasks, /\* Built-in plugin of Hvigor. It cannot be modified. \*/
      plugins: \[
        turboTransProtobufPlugin({
          saveDir: 'src/main/ets/protobuf', // 保存生成代码的目录
          ignoreModuleNames: \['TurboTransCore', 'PerformanceBaseline', 'TurboTransProtobufCommon'\], // 忽略的模块
          sendable: true, // 是否开启sendable
          debug: false, // 是否开启debug
          scanDir: \['protobuf'\], // 扫描目录
        }),
      \]       /\* Custom plugin to extend the functionality of Hvigor. \*/
    }
    
2.  编写proto文件。
    
    .proto文件需要放置在工程根目录的hvigorfile.ts文件中turboTransProtobufPlugin插件配置的scanDir扫描目录下才能生效，如示例工程中.proto位于entry/protobuf/test\_pb.proto。
    
    syntax = "proto3";
    
    message test\_pb {
        int32 value\_int32 = 1;
        int64 value\_int64 = 2;
        uint32 value\_uint32 = 3;
        uint64 value\_uint64 = 4;
        sint32 value\_sint32 = 5;
        sint64 value\_sint64 = 6;
        fixed32 value\_fixed32 = 7;
        fixed64 value\_fixed64 = 8;
        sfixed32 value\_sfixed32 = 9;
        sfixed64 value\_sfixed64 = 10;
        float value\_float = 11;
        double value\_double = 12;
        bool value\_bool = 13;
        string value\_string = 14;
        bytes value\_bytes = 15;
    }
    
3.  构建生成Sendable消息类。
    
    构建后，在工程根目录的hvigorfile.ts文件中turboTransProtobufPlugin插件配置的saveDir结果目录下生成Sendable消息类。如示例工程中会生成entry/src/main/ets/protobuf/test\_pb.ets。
    
    *   test\_pb.create()：创建实例。
    *   test\_pb.encode(message)：将实例编码为ArrayBuffer。
    *   test\_pb.decode(buffer)：将二进制数据解码回test\_pb。
4.  创建Sendable消息对象并进行编码与解码。
    
    使用生成类提供的encode/decode完成二进制序列化/反序列化并且解码后得到的消息对象仍可继续作为Sendable对象向后传递。test\_pb经过编解码后仍然保留Sendable特性，因此也可以像JSON路径生成的Sendable对象一样，继续作为并发任务的返回值进入UI绑定流程。以下示例中testProtobuf方法先创建test\_pb实例，再编码为二进制，最后解码回test\_pb并校验其仍为Sendable对象。
    
    import { test\_pb } from '../protobuf/test\_pb'
    import { ArkTSUtils, collections } from '@kit.ArkTS';
    
    function testCreate() {
      const obj = test\_pb.create();
      obj.value\_int32 = 1;
      obj.value\_int64 = 2;
      obj.value\_uint32 = 3;
      obj.value\_uint64 = 4;
      obj.value\_sint32 = 5;
      obj.value\_sint64 = 6;
      obj.value\_fixed32 = 7;
      obj.value\_fixed64 = 8;
      obj.value\_sfixed32 = 9;
      obj.value\_sfixed64 = 10;
      obj.value\_float = 11;
      obj.value\_double = 12;
      obj.value\_bool = true;
      obj.value\_string = "TestProtobuf\_Success";
      obj.value\_bytes?.fill(13);
    
      if (ArkTSUtils.isSendable(obj)) {
        console.info("create a sendable object");
      }
    
      return obj;
    }
    
    function testencode(obj: test\_pb) {
      return test\_pb.encode(obj);
    }
    
    function testdecode(data: ArrayBuffer | collections.ArrayBuffer) {
      let obj = test\_pb.decode(data);
      console.info("expect value\_int32 " + obj?.value\_int32 + " = 1");
      console.info("expect value\_double " + obj?.value\_double + " = 12");
      if (ArkTSUtils.isSendable(obj)) {
        console.info("decode a sendable object");
      }
    }
    
    export function testProtobuf() {
      let obj = testCreate();
      let buf = testencode(obj);
      if (buf) {
        testdecode(buf);
      }
    }
    

#### 使用makeObserved将Sendable对象转换为可观察对象

使用[UIUtils.makeObserved()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-statemanagement#makeobserved)方法可以将Sendable对象转换为可观察对象。

*   定义并发任务。
*   使用taskpool.execute()获取并发任务返回的Sendable对象，再通过UIUtils.makeObserved()转为可观察对象。
*   当可观察对象的属性发生变化时，绑定的UI组件会自动刷新显示。

#### \[h2\]定义并发任务

定义observeJSON1与observeJSON2并发任务

import { LayoutS } from '../turbotrans\_JSON/layout';
import { testJSON1, testJSON2 } from '../turbotrans\_JSON/test1';

@Concurrent
export function observeJSON1(): LayoutS {
  return testJSON1();
}

@Concurrent
export function observeJSON2(): LayoutS {
  return testJSON2();
}

定义observeProtobuf并发任务

import { testProtobuf } from '../turbotrans\_protobuf/test1'

@Concurrent
export function observeProtobuf() {
  return testProtobuf();
}

#### \[h2\]通过taskpool执行异步任务并将Sendable对象转换为可观察对象

runTests()中通过taskpool.execute()执行并发任务，拿到返回的Sendable对象后，再用UIUtils.makeObserved()包装为可观察对象并写回UI状态。这样数据链路就变成了“并发任务返回 -> 转换为可观察对象 -> 组件自动刷新”。

执行observeJSON1并发任务返回Sendable对象工程示例如下：

runTests() {
  taskpool.execute(observeJSON1).then((res) => {
    this.layout = UIUtils.makeObserved(res as LayoutS);
  })
}

执行observeProtobuf并发任务返回Sendable对象工程示例如下：

runTestsPb() {
  taskpool.execute(observeProtobuf).then((res: test\_pb) => {
    this.pb = UIUtils.makeObserved(res)
  })
}

#### \[h2\]创建UI组件实时观察Sendable对象属性变化

Index.ets中通过@Local状态保存任务返回值，再由Text组件绑定pb.value\_string和layout.type。其中，pb存储的是observeProtobuf返回的Sendable对象（test\_pb类型），layout存储的是observeJSON1返回的Sendable对象（LayoutS类型）。

observeJSON1返回的Sendable对象工程示例如下：

import { LayoutS } from '../turbotrans\_JSON/layout';
import { taskpool } from '@kit.ArkTS';
import { observeJSON1 } from './concurrentFunc';
import { UIUtils } from '@kit.ArkUI';

@Entry
@ComponentV2
struct Index {
  @Local message: string = 'Hello World';
  @Local layout: LayoutS = new LayoutS();

  build() {
    Column() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize($r('app.float.page\_text\_font\_size'))
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })

      Button('运行TaskPool测试')
        .width('80%')
        .height(50)
        .margin({ top: 20, bottom: 20 })
        .onClick(() => {
          this.runTests();
        })
        .id('button')

      Scroll() {
        Column() {

          Text(\`TestJSON: ${this.layout.type}\`)
            .fontSize(24)
            .textAlign(TextAlign.Start)
            .backgroundColor(Color.White)
            .padding(10)
            .borderRadius(8)
            .margin({ bottom: 10 })
            .width('100%')
        }
        .width('100%')
        .padding(10)
      }
      .height('60%')
      .width('100%')
    }
    .height('100%')
    .width('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }

// ...
}

observeProtobuf返回的Sendable对象工程示例如下：

@Entry
@ComponentV2
struct Index {
  @Local message: string = 'Hello World';
  @Local pb: test\_pb = test\_pb.create();

  build() {
    Column() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize($r('app.float.page\_text\_font\_size'))
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })

      Button('运行TaskPool测试')
        .width('80%')
        .height(50)
        .margin({ top: 20, bottom: 20 })
        .onClick(() => {
          this.runTestsPb();
        })
        .id('button')

      Scroll() {
        Column() {

          Text(\`TestProtobuf: ${this.pb.value\_string}\`)
            .fontSize(14)
            .textAlign(TextAlign.Start)
            .backgroundColor(Color.White)
            .padding(10)
            .borderRadius(8)
            .margin({ bottom: 10 })
            .width('100%')
        }
        .width('100%')
        .padding(10)
      }
      .height('60%')
      .width('100%')
    }
    .height('100%')
    .width('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }

// ...
}