鸿蒙RN搭建工程使用TurboModule

step 1 创建RN工程,然后手动创建harmony工程在根目录下

npx react-native@0.77.1 init awesome --version 0.77.1
之前这种创建工程的方式已经提示被废弃

# 2. 用社区 CLI 指定模板版本
npx @react-native-community/cli@latest init awesome --version 0.77.1 

为了使用加速 npm 包的下载,可以配置镜像源;如果关闭 SSL 证书校验还可以进一步加速下载,但是这会降低安全性,需用户评估后再使用
~/.npmrc 原来是

registry=https://registry.npmmirror.com/

改成了

strict-ssl=false
sslVerify=false
registry=https://repo.huaweicloud.com/repository/npm/

安装依赖

npm i
npm i @react-native-oh/react-native-harmony@0.77.18-1
npm i -D  @react-native-oh/react-native-harmony-cli@0.77.18

npm install 无响应解决
# 彻底清理
rm -rf node_modules package-lock.json

# 检查 npm 配置
npm config get registry

# 如果需要,设置为华为云镜像(从 package-lock.json 看已经在使用)
npm config set registry https://repo.huaweicloud.com/repository/npm/

# 安装依赖
npm install

通过DevEco Studio 在当前目录下创建harmony 工程

harmony/oh-package.json5 修改,添加react_native_openharmony.har 依赖,外部工程npm安装依赖后 node_modules下就有了

{
  "modelVersion": "6.0.0",
  "description": "Please describe the basic information.",
  "dependencies": {
    "@rnoh/react-native-openharmony": "file:../node_modules/@react-native-oh/react-native-harmony/react_native_openharmony.har"
  },
  "overrides": {
    "@rnoh/react-native-openharmony": "file:../node_modules/@react-native-oh/react-native-harmony/react_native_openharmony.har",
  },
  "devDependencies": {
    "@ohos/hypium": "1.0.24",
    "@ohos/hamock": "1.0.0"
  }
}

harmony/entry/oh-package.json5 下添加react_native_openharmony.har依赖

{
  "name": "entry",
  "version": "1.0.0",
  "description": "Please describe the basic information.",
  "main": "",
  "author": "",
  "license": "",
  "dependencies": {
    "@rnoh/react-native-openharmony": "file:../../node_modules/@react-native-oh/react-native-harmony/react_native_openharmony.har"
  }
}

修改后在DevEco Studio下去同步一下工程

harmony工程下== harmony/entry/src/main/ets/entryability/EntryAbility.ets==
改成继承RNAbility

export default class EntryAbility extends RNAbility {
  protected getPagePath(): string {
    return "pages/Index"
  }
  override onCreate(want: Want) {
    super.onCreate(want)
    hilog.info(0x0000, 'testTag', '%{public}s', 'EntryAbility onCreate');
  }
}

添加C++文件 harmony/entry/src/main/cpp/PackageProvider.cpp

#include "RNOH/PackageProvider.h"
#include "RNOH/generated/BaseRtnCalculatorPackage.h"
#include "generated/RNOHGeneratedPackage.h"
using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
        //std::make_shared<RNOHGeneratedPackage>(ctx),
        //std::make_shared<BaseRtnCalculatorPackage>(ctx)
    };
}

新增文件 harmony/entry/src/main/ets/RNPackagesFactory.ets

import { RNPackage, RNPackageContext } from "@rnoh/react-native-openharmony";
import GeneratedPackage from "./GeneratedPackage";

export function createRNPackages(ctx:RNPackageContext) :RNPackage[]{
  return [
    //new GeneratedPackage(ctx)
  ];
}

新增文件harmony/entry/src/main/ets/GeneratedPackage.ets

import {
  RNOHPackage,
  AnyThreadTurboModule,
  AnyThreadTurboModuleContext,
  UITurboModule,
  UITurboModuleContext
} from '@rnoh/react-native-openharmony';

import { TM } from "@rnoh/react-native-openharmony/generated"
import { CalculatorModule } from './turbomodule/CalculatorModule';

export default class GeneratedPackage extends RNOHPackage {
  override getUITurboModuleFactoryByNameMap(): Map<string, (ctx: UITurboModuleContext) => UITurboModule | null> {
    return new Map<string, ((ctx: UITurboModuleContext) => UITurboModule)>()
      .set(TM.RTNCalculator.NAME, (ctx) => new CalculatorModule(ctx)) //这里先返回空的map就可以了
  }

  override async createEagerUITurboModuleByNameMap(ctx: UITurboModuleContext): Promise<Map<string, UITurboModule>> {
    return new Map().set(TM.RTNCalculator.NAME, new CalculatorModule(ctx)) //这里先返回空的map就可以了
  }
}

修改文件 harmony/entry/src/main/ets/pages/Index.ets

import {
  AnyJSBundleProvider,
  ComponentBuilderContext,
  FileJSBundleProvider,
  MetroJSBundleProvider,
  RNApp,
  RNOHErrorDialog,
  RNOHLogger,
  TraceJSBundleProviderDecorator,
  RNOHCoreContext,
  ResourceJSBundleProvider
} from '@rnoh/react-native-openharmony'
import font from '@ohos.font'
import { createRNPackages } from '../RNPackagesFactory';
// import { componentBuilder } from "@react-native-ohos/react-native-screens"
// import { LottieAnimationView, LOTTIE_TYPE } from "@react-native-ohos/lottie-react-native";
import { KeyboardAvoidMode } from '@kit.ArkUI';

const arkTsComponentNames: Array<string> = []
@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {}
const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent)
@Entry
@Component
struct Index{
  @StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined
  @State shouldShow: boolean = false
  private logger!: RNOHLogger
  aboutToAppear(): void {
    this.logger = this.rnohCoreContext!.logger.clone("Index")
    const stopTracing = this.logger.clone("aboutToAppear").startTracing()
    // for (const customFont of fonts) {
    //   font.registerFont(customFont)
    // }
    this.shouldShow = true
    stopTracing()
  }
  onBackPress(): boolean | void {
    this.rnohCoreContext!.dispatchBackPress()
    return true
  }

  build() {
    Column() {
      if (this.rnohCoreContext && this.shouldShow) {
        if (this.rnohCoreContext?.isDebugModeEnabled) {
          RNOHErrorDialog({ ctx: this.rnohCoreContext })
        }
        RNApp({
          rnInstanceConfig: {
            createRNPackages,
            enableNDKTextMeasuring: true, // 该项必须为true,用于开启NDK文本测算
            enableBackgroundExecutor: false,
            enableCAPIArchitecture: true, // 该项必须为true,用于开启CAPI
            arkTsComponentNames: arkTsComponentNames
          },
          initialProps: { "foo": "bar" } as Record<string, string>,
          appKey: "awesome",//这个名称一定要和RNOH工程package.json 中的name保持一致
          wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder,
          onSetUp: (rnInstance) => {
            rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP")
          },
          jsBundleProvider: new TraceJSBundleProviderDecorator(
            new AnyJSBundleProvider([
              new MetroJSBundleProvider(),
              // NOTE: to load the bundle from file, place it in
              // `/data/app/el2/100/base/com.rnoh.tester/files/bundle.harmony.js`
              // on your device. The path mismatch is due to app sandboxing on OpenHarmony
              new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
              new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
              new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
            ]),
            this.rnohCoreContext.logger),
        })
      }
    }
    .height('100%')
    .width('100%')
  }

}

step 2

添加package.json. scripts节点。 这个bundle-harmony工具在react-native-harmony-cli里面,上面已经安装了依赖

"start": "hdc rport tcp:8081 tcp:8081 && react-native start",
"codegen": "react-native codegen-harmony --cpp-output-path ./harmony/entry/src/main/cpp/generated --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony",
"dev": "npm run codegen && react-native bundle-harmony --dev",

package.json 下添加节点 harmony 用来codegen使用

"harmony": {
    "alias": "rtn-calculator",
    "codegenConfig": [
      {
        "version": 1,
        "specPaths": [
          "src/specs/v1"
        ]
      },
      {
        "version": 2,
        "specPaths": [
          "src/specs/v2"
        ]
      }
    ]
  },
  "files": [
    "index.ts",
    "src/*"
  ],

harmony/entry/build-profile.json5 buildOption 里面添加

"externalNativeOptions": {
    "path": "./src/main/cpp/CMakeLists.txt",
    "arguments": "",
    "cppFlags": "",
},

添加 CMakeLists.txt文件 harmony/entry/src/main/cpp/CMakeLists.txt

project(rnapp)
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on) #给 生成构建系统(ninja / make)用的开关:ON 时 会把 完整编译命令 全部打印出来:[1/2] /path/to/clang++ -DWITH_HITRACE_SYSTRACE -I../../generated -std=c++17 ... -c xxx.cpp -o xxx.cpp.o; 默认 OFF 时 你执行 ninja / make 看到的只是简短一行:[1/2] Building CXX object CMakeFiles/xxx.cpp.o
set(WITH_HITRACE_SYSTRACE 1) #产生一个 CMake 变量,供 if(WITH_HITRACE_SYSTRACE) 这类 脚本逻辑 使用
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") #必须配置,内部用到了这个变量名,所以名字不能改
#set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../oh_modules")#定义oh_modules路径,方便其他变量简写
set(RNOH_CPP_DIR "${OH_MODULES}/@rnoh/react-native-openharmony/src/main/cpp") #必须配置
set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated/awasome") #必须配置。awasome 是根据生成的目录名称改变
#file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")
#set(rtn_calculator_generated_dir "${RNOH_GENERATED_DIR}/rtn_calculator")
file(GLOB_RECURSE rtn_calculator_generated_dir_src "${RNOH_GENERATED_DIR}/**/*.cpp")#当前目录 + 所有子目录,收集多层目录里生成的代码
file(GLOB rtn_calculator_package_src CONFIGURE_DEPENDS *.cpp)#收集同目录手写源文件,搜索范围为当前 CMakeLists.txt 所在目录 CONFIGURE_DEPENDS 让 “同目录通配符” 也能增量感知文件增减,只对 GLOB 有效,GLOB_RECURSE 本身已自带目录树监控,再加会被忽略。
add_compile_definitions(WITH_HITRACE_SYSTRACE) #CMake 3.12 起提供的命令,等价于在 所有编译器命令行 末尾追加 -DWITH_HITRACE_SYSTRACE,也就是定义一个 布尔宏:-DWITH_HITRACE_SYSTRACE
add_subdirectory("${RNOH_CPP_DIR}" ./rn) #把 RN 引擎(C++ 源码树) 整个拉进来一起编译,并告诉 CMake:源码在 ${RNOH_CPP_DIR},生成的中间产物(build artifacts)放到当前二进制目录下的 ./rn 子文件夹里
add_library(rnoh_app SHARED
#    ${GENERATED_CPP_FILES}
    ${rtn_calculator_package_src}
    ${rtn_calculator_generated_dir_src}
#    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_include_directories(rnoh_app PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${RNOH_GENERATED_DIR}
    ${RNOH_GENERATED_DIR}/rtn_calculator
)
target_link_libraries(rnoh_app PUBLIC rnoh) #target_link_libraries(<目标> <可见性> <库>...)这一行把 “rnoh” 这个静态库 真正挂到最终动态库 librnoh_app.so 上,并告诉编译器:所有依赖 rnoh_app 的目标,也能 自动拿到 rnoh 的头文件搜索路径和链接标志


打开项目根目录下metro.config.js,并添加 OpenHarmony 的适配代码。配置文件的详细介绍,可以参考++React Native 中文网++。修改完成后的文件内容如下:

const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config');
const {createHarmonyMetroConfig} = require('@react-native-oh/react-native-harmony/metro.config');

/**
* @type {import("metro-config").ConfigT}
*/
const config = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), createHarmonyMetroConfig({
  reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
}), config);

目录下运行生成 bundle 文件的命令。运行成功后,会在 AwesomeProject/harmony/entry/src/main/resources/rawfile 目录下生成 bundle.harmony.js 和 assets 文件夹,assets 用来存放图片(如果 bundle 中不涉及本地图片,则没有 assets 文件夹)。

清理demo中用到的组件,只展示一个Text,不然运行下面会报错找不到Image 等组件,默认是RN官方的demo界面

npm run dev

警告信息处理
WARN  Watchman `watch-project` returned a warning: Recrawled this watch 27 times, most recently because:
MustScanSubDirs UserDroppedTo resolve, please review the information on
https://facebook.github.io/watchman/docs/troubleshooting.html#recrawl
To clear this warning, run:
`watchman watch-del '/Users/fangzhen/Documents/react_native/example/awesome' ; watchman watch-project '/Users/fangzhen/Documents/react_native/example/awesome'`

 WARN  Watchman `query` returned a warning: Recrawled this watch 27 times, most recently because:
MustScanSubDirs UserDroppedTo resolve, please review the information on
https://facebook.github.io/watchman/docs/troubleshooting.html#recrawl
To clear this warning, run:
`watchman watch-del '/Users/fangzhen/Documents/react_native/example/awesome' ; watchman watch-project '/Users/fangzhen/Documents/react_native/example/awesome'`

watchman watch-del '/Users/fangzhen/Documents/react_native/example/awesome'
watchman watch-project '/Users/fangzhen/Documents/react_native/example/awesome'

awesome/src/specs/v2/NativeCalculator.ts RTNCalculator就是导出模块的名称,后续CPP,ArkTS 模块里面都是这个名称

import {TurboModuleRegistry} from 'react-native';
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';

export interface Spec extends TurboModule {
  add(a: number, b: number): Promise<number>;
}
export default TurboModuleRegistry.get<Spec>('RTNCalculator') as Spec | null;

step 3

fangzhen@fangzhens-MacBook-Pro awesome % npm run codegen

> awesome@0.0.1 codegen
> react-native codegen-harmony --cpp-output-path ./harmony/entry/src/main/cpp/generated --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony

• harmony/entry/oh_modules/@rnoh/react-native-openharmony/generated/components/ts.ts
• harmony/entry/oh_modules/@rnoh/react-native-openharmony/generated/index.ets
• harmony/entry/oh_modules/@rnoh/react-native-openharmony/generated/ts.ts
• harmony/entry/oh_modules/@rnoh/react-native-openharmony/generated/turboModules/RTNCalculator.ts
• harmony/entry/oh_modules/@rnoh/react-native-openharmony/generated/turboModules/ts.ts
• harmony/entry/src/main/cpp/generated/RNOHGeneratedPackage.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/RNOH/generated/BaseRtnCalculatorPackage.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/RNOH/generated/turbo_modules/RTNCalculator.cpp
• harmony/entry/src/main/cpp/generated/rtn_calculator/RNOH/generated/turbo_modules/RTNCalculator.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/ComponentDescriptors.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/EventEmitters.cpp
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/EventEmitters.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/Props.cpp
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/Props.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/ShadowNodes.cpp
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/ShadowNodes.h
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/States.cpp
• harmony/entry/src/main/cpp/generated/rtn_calculator/react/renderer/components/rtn_calculator/States.h

info Generated 18 file(s)

fangzhen@fangzhens-MacBook-Pro awesome %



harmony/entry/src/main/ets/turbomodule/CalculatorModule.ets

import { UITurboModule,AnyThreadTurboModule } from '@rnoh/react-native-openharmony';
import { TM } from '@rnoh/react-native-openharmony/generated/ts';


export class CalculatorModule extends UITurboModule implements TM.RTNCalculator.Spec {
  say(): string {
    return "Hello World 123 123"
  }
  add(a: number, b: number): Promise<number>{
    // this.ctx.rnInstance.emitDeviceEvent("clickMarqueeEvent", { params: { age: 18 } })
    return Promise.resolve(a+b);
  }
}

生成的c++代码中可以自定义写法,下面的例子,没有调用arkts里面的say函数实现,而是直接使用c++原生实现返回JSI DEMO字符串,重新实现了add函数,返回promise

/**
 * This code was generated by "react-native codegen-harmony"
 *
 * Do not edit this file as changes may cause incorrect behavior and will be
 * lost once the code is regenerated.
 *
 * @generatorVersion: 2
 */

#include "RTNCalculator.h"

namespace rnoh {
using namespace facebook;

static jsi::Value __hostFunction_RTNCalculatorCxxModuleSpecJSI_say(jsi::Runtime &rt, react::TurboModule &turboModule,
                                                                   const jsi::Value *args, size_t count) {
    const char * str = "JSI DEMO";
    return jsi::String::createFromUtf8(rt, str);
}
static jsi::Value __hostFunction_RTNCalculatorCxxModuleSpecJSI_add(jsi::Runtime &rt, react::TurboModule &turboModule,
                                                                   const jsi::Value *args, size_t count) {
    double a = args[0].getNumber();
    double b = args[1].getNumber();
    // 获取全局Promise构造函数
    jsi::Object global = rt.global();
    jsi::Function promiseCtor = global.getPropertyAsFunction(rt, "Promise");
    
    // 创建executor函数
    jsi::Function executor = jsi::Function::createFromHostFunction(
        rt,
        jsi::PropNameID::forAscii(rt, "executor"),
        2, // resolve和reject参数
        [a, b](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *executorArgs, size_t executorCount) -> jsi::Value {
            // 获取resolve函数
            jsi::Function resolve = executorArgs[0].asObject(rt).asFunction(rt);
            // 执行计算
            double result = (a + b)*100;
            // 调用resolve
            resolve.call(rt, jsi::Value(rt, result));
            return jsi::Value::undefined();
        }
    );
    // 创建并返回Promise实例
    return promiseCtor.callAsConstructor(rt, executor);
}

简写
static jsi::Value __hostFunction_RTNCalculatorCxxModuleSpecJSI_add(jsi::Runtime &rt, react::TurboModule &turboModule,
                                                                   const jsi::Value *args, size_t count) {
    double a = args[0].getNumber();
    double b = args[1].getNumber();
    return rt.global()
        .getPropertyAsFunction(rt, "Promise")
        .callAsConstructor(rt, jsi::Function::createFromHostFunction(
                                   rt, jsi::PropNameID::forAscii(rt, ""), 2,
                                   [a, b](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *execArgs, size_t) {
                                       execArgs[0].asObject(rt).asFunction(rt).call(rt, jsi::Value(rt, a + b));
                                       return jsi::Value::undefined();
                                   }));
}

RTNCalculator::RTNCalculator(const ArkTSTurboModule::Context ctx, const std::string name)
    : ArkTSTurboModule(ctx, name) {
    methodMap_ = {
//        ARK_ASYNC_METHOD_METADATA(add, 2),
//        ARK_METHOD_METADATA(say, 0),
    };
    methodMap_["say"] = MethodMetadata{0, __hostFunction_RTNCalculatorCxxModuleSpecJSI_say};
    methodMap_["add"] = MethodMetadata{2, __hostFunction_RTNCalculatorCxxModuleSpecJSI_add};
}

} // namespace rnoh

RN:TurboModule 组件。 Fabric 组件、Autolinking

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐