React Native 三方库 react-native-version-number 鸿蒙适配实战:从零到版本信息展示

本文记录了将开源 React Native 三方库 react-native-version-number 安装集成到 HarmonyOS NEXT 平台的完整过程,涵盖 npm 包安装、Metro 重定向配置、OHOS 原生源码集成、Codegen 桥接代码手动生成、C++ 原生层配置,以及踩坑排障的完整复盘。


一、背景

1.1 三方库简介

react-native-version-number 是一个轻量级 React Native 库,用于获取应用的版本号、构建号和包名标识。在 iOS 和 Android 上,它通过读取原生配置(Info.plist / BuildConfig)获取版本信息。

鸿蒙适配版本由 CPF-RN 团队维护,使用鸿蒙原生 @kit.AbilityKitbundleManager API 读取应用包信息。

1.2 适配目标

目标项 说明
三方库 react-native-version-number@react-native-ohos/react-native-version-number
RN 框架版本 @rnoh/react-native-openharmony 0.82.30
React Native 版本 0.82.1
目标平台 HarmonyOS NEXT (API 12+)
目标能力 获取 appVersion、buildVersion、bundleIdentifier

1.3 版本兼容性

鸿蒙适配版本 原库版本 兼容 RN 版本 支持 Autolink 最低 API npm 包版本
~0.5.0 ~0.5.0 0.82.* ✅ 是 API12+ npm
~0.4.0 ~0.4.0 0.77.* ❌ 否 API12+ npm
~0.3.7 ~0.3.7 0.72.* ✅ 是 API12+ npm
<=0.3.6-0.0.1 ≤0.3.6 0.72.* ❌ 否 API12+ npm

⚠️ 版本注意:本文使用 0.5.0-beta.1,对应 RN 0.82.x。如果你使用其他 RN 版本,请选择对应的鸿蒙适配版本。

1.4 支持能力一览

API 功能 鸿蒙支持
appVersion 应用版本号
buildVersion 构建版本号
bundleIdentifier 包名标识

二、适配路线图

整个适配过程分为以下阶段:

阶段 内容 关键产出
阶段一 JS 端依赖安装 package.json 新增依赖
阶段二 Metro 重定向验证 bundle.harmony.js 中 import 自动替换
阶段三 OHOS 端源码集成 version_number/ 目录 3 个 ArkTS 文件
阶段四 Codegen 桥接代码手动生成 generated/ 目录 3 个新文件
阶段五 C++ 原生层配置 CMakeLists.txt + RNOHGeneratedPackage.h
阶段六 App 业务代码与 Bundle 构建 App.tsx + bundle.harmony.js
阶段七 构建部署与验证 设备端版本信息展示

三、逐步适配过程

阶段一:JS 端依赖安装

1.1 安装鸿蒙适配版本

在 RN 项目根目录下安装:

cd AwesomeProject

# 安装对应 RN 0.82.x 的鸿蒙适配版本
npm install @react-native-ohos/react-native-version-number@0.5.0-beta.1

npm 安装结果

安装完成后,package.json 中会新增:

{
  "dependencies": {
    "@react-native-ohos/react-native-version-number": "^0.5.0-beta.1"
  }
}
1.2 验证安装
cat node_modules/@react-native-ohos/react-native-version-number/package.json | python3 -c "import sys,json; d=json.load(sys.stdin); print('version:', d.get('version'))"

输出:

version: 0.5.0-beta.1
1.3 确认 harmony 目录结构

安装后的 npm 包自带 OHOS 原生源码:

ls -la node_modules/@react-native-ohos/react-native-version-number/harmony/

输出:

drwxr-xr-x  4  rnoh_version_number
-rw-r--r--  1  rnoh_version_number.har

其中 rnoh_version_number.har 是预编译包,rnoh_version_number/ 是源码目录。我们采用源码直接集成方式。

1.4 查看 JS 端入口文件
// node_modules/@react-native-ohos/react-native-version-number/index.js
import { NativeModules, TurboModuleRegistry } from 'react-native';

const { RNVersionNumber } = TurboModuleRegistry 
  ? TurboModuleRegistry.get('RNVersionNumber').getConstants() 
  : NativeModules;

type VersionObject = {
  appVersion: ?string,
  buildVersion: ?string,
  bundleIdentifier: ?string
};

const VersionNumber: VersionObject = {
  appVersion: RNVersionNumber && RNVersionNumber.appVersion,
  buildVersion: RNVersionNumber && RNVersionNumber.buildVersion,
  bundleIdentifier: RNVersionNumber && RNVersionNumber.bundleIdentifier
};

export default VersionNumber;

💡 JS 端通过 TurboModuleRegistry.get('RNVersionNumber') 获取 TurboModule,然后调用 getConstants() 获取版本信息常量。模块名 RNVersionNumber 是三端桥接的关键标识。


阶段二:Metro 重定向验证

2.1 Metro 配置

确保 AwesomeProject/metro.config.js 已配置 Harmony Metro 插件:

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

const config = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), createHarmonyMetroConfig({
  reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
}), config);
2.2 构建 Bundle 并验证重定向
cd AwesomeProject
npx react-native bundle-harmony --dev false

输出中可以看到重定向信息:

[INFO] Redirected imports to 2 harmony-specific third-party package(s):
[INFO] • react-native-tts → @react-native-ohos/react-native-tts
[INFO] • react-native-version-number → @react-native-ohos/react-native-version-number

Metro 重定向验证

这说明 import VersionNumber from 'react-native-version-number' 会被自动重定向到鸿蒙适配版本 @react-native-ohos/react-native-version-number


阶段三:OHOS 端源码集成

Myrndemo/entry/src/main/ets/ 下创建 version_number/ 目录,放置 3 个 ArkTS 文件。

3.1 目录结构
entry/src/main/ets/
├── version_number/
│   ├── RNVersionNumberPackage.ets    # RN Package 注册入口
│   ├── RNVersionNumberTurboModule.ts # TurboModule 实现(桥接层)
│   └── Logger.ts                     # 日志工具
├── tts/                              # (已有)TTS 相关文件
├── generated/                        # Codegen 生成(阶段四产出)
│   ├── ts.ts
│   ├── index.ets
│   ├── turboModules/
│   │   ├── TTSNativeModule.ts
│   │   ├── RNVersionNumber.ts        # 新增
│   │   └── ts.ts
│   └── components/
│       └── ts.ts
├── RNPackagesFactory.ets
└── pages/Index.ets
3.2 从 npm 包复制源码
# 创建目标目录
mkdir -p Myrndemo/entry/src/main/ets/version_number

# 复制 3 个源文件
cp AwesomeProject/node_modules/@react-native-ohos/react-native-version-number/harmony/rnoh_version_number/src/main/ets/RNVersionNumberPackage.ets \
   Myrndemo/entry/src/main/ets/version_number/

cp AwesomeProject/node_modules/@react-native-ohos/react-native-version-number/harmony/rnoh_version_number/src/main/ets/RNVersionNumberTurboModule.ts \
   Myrndemo/entry/src/main/ets/version_number/

cp AwesomeProject/node_modules/@react-native-ohos/react-native-version-number/harmony/rnoh_version_number/src/main/ets/Logger.ts \
   Myrndemo/entry/src/main/ets/version_number/
3.3 修改 import 路径

npm 包中的源码 import 路径是 ./generated/ts(相对于库内部),需要修改为 ../generated/ts(相对于项目结构):

RNVersionNumberTurboModule.ts 修改:

- import { TM } from "./generated/ts";
+ import { TM } from '../generated/ts';

RNVersionNumberPackage.ets 修改:

- import { TM } from "./generated/ts";
+ import { TM } from '../generated/ts';
3.4 RNVersionNumberPackage.ets — Package 注册入口
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
 * Use of this source code is governed by a MIT license that can be
 * found in the LICENSE file.
 */

import { RNOHPackage, TurboModulesFactory } from '@rnoh/react-native-openharmony';
import type { TurboModule, TurboModuleContext, } from '@rnoh/react-native-openharmony';
import { TM } from '../generated/ts';
import { RNVersionNumberTurboModule } from './RNVersionNumberTurboModule';

class RNVersionNumberFactory extends TurboModulesFactory {
  createTurboModule(name: string): TurboModule | null {
    if (name === 'RNVersionNumber' ||  name === TM.RNVersionNumber.NAME) {
      return new RNVersionNumberTurboModule(this.ctx);
    }
    return null;
  }

  hasTurboModule(name: string): boolean {
    return name === 'RNVersionNumber' || name === TM.RNVersionNumber.NAME;
  }
}

export class RNVersionNumberPackage extends RNOHPackage {
  createTurboModulesFactory(ctx: TurboModuleContext): TurboModulesFactory {
    return new RNVersionNumberFactory(ctx);
  }
}

💡 注意这里用的是 RNOHPackage(0.82.x 版本),而不是旧版的 RNPackage

3.5 RNVersionNumberTurboModule.ts — TurboModule 实现
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
 * Use of this source code is governed by a MIT license that can be
 * found in the LICENSE file.
 */

import { bundleManager } from '@kit.AbilityKit';
import { BusinessError } from '@ohos.base';
import { TurboModule } from '@rnoh/react-native-openharmony/ts';
import { TM } from '../generated/ts';
import Logger from './Logger';

const TAG = 'RNVersionNumber';

interface VersionInfo {
  appVersion: string,
  buildVersion: string,
  bundleIdentifier: string
}

export class RNVersionNumberTurboModule extends TurboModule implements TM.RNVersionNumber.Spec {
  constructor(ctx) {
    super(ctx);
  }

  getConstants(): Object {
    let RNVersionNumber: VersionInfo = {
      appVersion: '',
      buildVersion: '',
      bundleIdentifier: ''
    };
    try {
      const bundleInfos = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
      RNVersionNumber.bundleIdentifier = bundleInfos.name;
      RNVersionNumber.appVersion = bundleInfos.versionName;
      RNVersionNumber.buildVersion = String(bundleInfos.versionCode);
      Logger.info(TAG, JSON.stringify(bundleInfos));
    } catch (err) {
      const message = (err as BusinessError).message;
      Logger.error(TAG, JSON.stringify(message))
    }
    return { RNVersionNumber }
  }
}

💡 关键实现

  • 使用 bundleManager.getBundleInfoForSelfSync() 同步获取当前应用包信息
  • bundleInfos.namebundleIdentifier(包名,如 com.nutpi.rndemo
  • bundleInfos.versionNameappVersion(语义版本号,如 1.0.0
  • bundleInfos.versionCodebuildVersion(整型构建号,转为字符串,如 1000000
  • getConstants() 是同步方法,返回的对象会作为 TurboModule 的常量暴露给 JS 端
3.6 Logger.ts — 日志工具
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
 * Use of this source code is governed by a MIT license that can be
 * found in the LICENSE file.
 */

import hilog from '@ohos.hilog';

class Logger {
  private domain: number;
  private prefix: string;
  private format: string = '%{public}s, %{public}s';
  private isDebug: boolean;

  constructor(prefix: string = 'MyApp', domain: number = 0xFF00, isDebug = false) {
    this.prefix = prefix;
    this.domain = domain;
    this.isDebug = isDebug;
  }

  debug(...args: string[]): void {
    if (this.isDebug) {
      hilog.debug(this.domain, this.prefix, this.format, args);
    }
  }

  info(...args: string[]): void {
    hilog.info(this.domain, this.prefix, this.format, args);
  }

  warn(...args: string[]): void {
    hilog.warn(this.domain, this.prefix, this.format, args);
  }

  error(...args: string[]): void {
    hilog.error(this.domain, this.prefix, this.format, args);
  }
}

export default new Logger('RNVersionNumber', 0xFF00, false)

阶段四:Codegen 桥接代码手动生成

4.1 为什么需要手动生成?

执行 Codegen 命令后,只生成了 TTS 的桥接代码,没有生成 RNVersionNumber 的

cd AwesomeProject
RNOH_C_API_ARCH=1 npx react-native codegen-harmony \
  --ets-output-path ../Myrndemo/entry/src/main/ets/generated \
  --cpp-output-path ../Myrndemo/entry/src/main/cpp/generated \
  --project-root-path . \
  --no-safety-check

输出:

• ../Myrndemo/entry/src/main/cpp/generated/RNOHGeneratedPackage.h
• ../Myrndemo/entry/src/main/cpp/generated/TTSNativeModule.cpp
• ../Myrndemo/entry/src/main/cpp/generated/TTSNativeModule.h
• ../Myrndemo/entry/src/main/ets/generated/components/ts.ts
• ../Myrndemo/entry/src/main/ets/generated/index.ets
• ../Myrndemo/entry/src/main/ets/generated/ts.ts
• ../Myrndemo/entry/src/main/ets/generated/turboModules/TTSNativeModule.ts
• ../Myrndemo/entry/src/main/ets/generated/turboModules/ts.ts

info Generated 8 file(s)

原因@react-native-ohos/react-native-version-numberNativeRNVersionNumber.ts 没有使用 @codegenConfig 注解,Codegen 扫描不到它。需要手动创建桥接文件。

4.2 创建 ETS 桥接类型文件

generated/turboModules/RNVersionNumber.ts

/**
 * 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.
 */

import { Tag } from "@rnoh/react-native-openharmony/ts"

export namespace RNVersionNumber {
  export const NAME = 'RNVersionNumber' as const

  export interface Spec {
    getConstants(): {};
  }
}

💡 NAME 常量值为 'RNVersionNumber',与 JS 端 TurboModuleRegistry.get('RNVersionNumber') 的模块名一致。

4.3 更新 ETS 导出入口

generated/turboModules/ts.ts — 新增 RNVersionNumber 导出:

/**
 * 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.
 */

export * from "./TTSNativeModule"
export * from "./RNVersionNumber"
4.4 创建 C++ 桥接文件

generated/RNVersionNumber.h

/**
 * 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.
 */

#pragma once

#include "RNOH/ArkTSTurboModule.h"

namespace rnoh {

class JSI_EXPORT RNVersionNumber : public ArkTSTurboModule {
  public:
    RNVersionNumber(const ArkTSTurboModule::Context ctx, const std::string name);
};

} // namespace rnoh

generated/RNVersionNumber.cpp

/**
 * 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.
 */

#include "RNVersionNumber.h"

namespace rnoh {
using namespace facebook;

RNVersionNumber::RNVersionNumber(const ArkTSTurboModule::Context ctx, const std::string name) : ArkTSTurboModule(ctx, name) {
    methodMap_ = {
        ARK_METHOD_METADATA(getConstants, 0),
    };
}

} // namespace rnoh

💡 ARK_METHOD_METADATA(getConstants, 0) 表示 getConstants 是同步方法,参数个数为 0。注意这里用的是 ARK_METHOD_METADATA(同步),而不是 ARK_ASYNC_METHOD_METADATA(异步/返回 Promise)。

4.5 更新 C++ Package 注册

generated/RNOHGeneratedPackage.h — 新增 RNVersionNumber:

/**
 * 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: 1
 */

#pragma once

#include "RNOH/Package.h"
#include "RNOH/ArkTSTurboModule.h"
#include "generated/TTSNativeModule.h"
#include "generated/RNVersionNumber.h"       // ← 新增

namespace rnoh {

class RNOHGeneratedPackageTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
  public:
    SharedTurboModule createTurboModule(Context ctx, const std::string &name) const override {
        if (name == "TTSNativeModule") {
            return std::make_shared<TTSNativeModule>(ctx, name);
        }
        if (name == "RNVersionNumber") {      // ← 新增
            return std::make_shared<RNVersionNumber>(ctx, name);
        }
        return nullptr;
    };
};

// ... 其余代码不变
4.6 新增文件总览
文件路径 类型 说明
ets/generated/turboModules/RNVersionNumber.ts ETS TM 类型定义(NAME + Spec 接口)
cpp/generated/RNVersionNumber.h C++ TurboModule 声明
cpp/generated/RNVersionNumber.cpp C++ TurboModule 实现 + 方法映射表

修改的文件

文件路径 修改内容
ets/generated/turboModules/ts.ts 新增 export * from "./RNVersionNumber"
cpp/generated/RNOHGeneratedPackage.h 新增 #includeif (name == "RNVersionNumber") 分支

阶段五:C++ 原生层配置

5.1 更新 CMakeLists.txt
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")

set(RNOH_CPP_DIR "${OH_MODULE_DIR}/@rnoh/react-native-openharmony/src/main/cpp")
set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated")
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
add_compile_definitions(WITH_HITRACE_SYSTRACE)
set(WITH_HITRACE_SYSTRACE 1)

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

add_library(rnoh_app SHARED
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
    "${RNOH_GENERATED_DIR}/RNOHGeneratedPackage.h"
    "${RNOH_GENERATED_DIR}/TTSNativeModule.cpp"
    "${RNOH_GENERATED_DIR}/RNVersionNumber.cpp"      # ← 新增
)

target_include_directories(rnoh_app PUBLIC
    "${CMAKE_CURRENT_SOURCE_DIR}"
    "${RNOH_GENERATED_DIR}"
)

target_link_libraries(rnoh_app PUBLIC rnoh)

💡 相比之前的 TTS 集成,仅新增了 "${RNOH_GENERATED_DIR}/RNVersionNumber.cpp" 一行。

5.2 PackageProvider.cpp — 无需修改
#include "RNOH/PackageProvider.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),
    };
}

💡 RNOHGeneratedPackage 内部已经注册了 RNVersionNumber,所以 PackageProvider.cpp 无需修改。

5.3 更新 RNPackagesFactory.ets
import { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony/ts';
import { RNTTSPackage } from './tts/RNTTSPackage';
import { RNVersionNumberPackage } from './version_number/RNVersionNumberPackage.ets';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new RNTTSPackage(ctx),
    new RNVersionNumberPackage(ctx),      // ← 新增
  ];
}

阶段六:App 业务代码与 Bundle 构建

6.1 App.tsx

在原有 TTS Demo 的基础上,新增版本信息展示卡片:

import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  ScrollView,
  Alert,
} from 'react-native';
import Tts from '@react-native-ohos/react-native-tts';
import VersionNumber from 'react-native-version-number';

function App() {
  const [text, setText] = useState('你好,鸿蒙!欢迎使用 React Native 语音合成功能。');
  const [status, setStatus] = useState('就绪');
  const [isSpeaking, setIsSpeaking] = useState(false);
  const [rate, setRate] = useState(1.0);
  const [pitch, setPitch] = useState(1.0);

  useEffect(() => {
    Tts.getInitStatus().then(() => {
      setStatus('TTS 引擎已就绪');
    }).catch((err) => {
      setStatus('TTS 引擎初始化失败: ' + JSON.stringify(err));
    });

    const onStart = Tts.addEventListener('tts-start', (event) => {
      setIsSpeaking(true);
      setStatus('正在朗读...');
    });

    const onFinish = Tts.addEventListener('tts-finish', (event) => {
      setIsSpeaking(false);
      setStatus('朗读完成');
    });

    const onError = Tts.addEventListener('tts-error', (event) => {
      setIsSpeaking(false);
      setStatus('朗读出错');
    });

    const onCancel = Tts.addEventListener('tts-cancel', (event) => {
      setIsSpeaking(false);
      setStatus('已停止');
    });

    return () => {
      onStart.remove();
      onFinish.remove();
      onError.remove();
      onCancel.remove();
    };
  }, []);

  const handleSpeak = () => {
    if (!text.trim()) {
      Alert.alert('提示', '请输入要朗读的文本');
      return;
    }
    Tts.speak(text);
  };

  const handleStop = () => {
    Tts.stop(true);
  };

  const handleRateChange = (delta) => {
    const newRate = Math.max(0.5, Math.min(2.0, rate + delta));
    setRate(newRate);
    Tts.setDefaultRate(newRate);
  };

  const handlePitchChange = (delta) => {
    const newPitch = Math.max(0.5, Math.min(2.0, pitch + delta));
    setPitch(newPitch);
    Tts.setDefaultPitch(newPitch);
  };

  return (
    <ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
      <Text style={styles.title}>RN OHOS Demo</Text>

      {/* 版本信息卡片 */}
      <View style={styles.card}>
        <Text style={styles.cardTitle}>应用版本信息</Text>
        <View style={styles.infoRow}>
          <Text style={styles.infoLabel}>应用版本</Text>
          <Text style={styles.infoValue}>{VersionNumber.appVersion || 'N/A'}</Text>
        </View>
        <View style={styles.infoRow}>
          <Text style={styles.infoLabel}>构建版本</Text>
          <Text style={styles.infoValue}>{VersionNumber.buildVersion || 'N/A'}</Text>
        </View>
        <View style={styles.infoRow}>
          <Text style={styles.infoLabel}>包名标识</Text>
          <Text style={styles.infoValue}>{VersionNumber.bundleIdentifier || 'N/A'}</Text>
        </View>
      </View>

      {/* TTS 卡片 */}
      <View style={styles.card}>
        <Text style={styles.cardTitle}>语音合成</Text>
        {/* ... TTS 相关 UI ... */}
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f0f2f5',
  },
  contentContainer: {
    padding: 16,
    paddingBottom: 40,
  },
  title: {
    fontSize: 22,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 16,
    color: '#1a1a1a',
  },
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
    elevation: 2,
  },
  cardTitle: {
    fontSize: 17,
    fontWeight: '600',
    color: '#333',
    marginBottom: 12,
  },
  infoRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 6,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  infoLabel: {
    fontSize: 14,
    color: '#666',
  },
  infoValue: {
    fontSize: 14,
    color: '#1a1a1a',
    fontWeight: '500',
  },
  // ... 其他样式 ...
});

export default App;

💡 VersionNumber.appVersionVersionNumber.buildVersionVersionNumber.bundleIdentifier 三个属性在模块加载时就会通过 getConstants() 获取,无需异步调用。

6.2 构建 JS Bundle
cd AwesomeProject
npx react-native bundle-harmony --dev false

JS Bundle 构建

输出确认重定向:

[INFO] Redirected imports to 2 harmony-specific third-party package(s):
[INFO] • react-native-tts → @react-native-ohos/react-native-tts
[INFO] • react-native-version-number → @react-native-ohos/react-native-version-number

info Created harmony/entry/src/main/resources/rawfile/bundle.harmony.js
6.3 复制 Bundle 到 OHOS 工程
cp AwesomeProject/harmony/entry/src/main/resources/rawfile/bundle.harmony.js \
   Myrndemo/entry/src/main/resources/rawfile/bundle.harmony.js

阶段七:构建部署与验证

7.1 构建 HAP
cd Myrndemo
export RNOH_C_API_ARCH=1

/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  --mode module -p module=entry@default -p product=default assembleHap --no-daemon

HAP 构建成功

构建成功输出:

> hvigor BUILD SUCCESSFUL in 6 s 945 ms
7.2 部署到设备
hdc install -r Myrndemo/entry/build/default/outputs/default/entry-default-signed.hap

输出:

[Info] App install path:... msg:install bundle successfully.
7.3 启动应用
hdc shell aa start -a EntryAbility -b com.nutpi.rndemo
7.4 验证日志
hdc hilog -x | grep -iE "RNVersionNumber|bundleInfo"

成功日志示例:

RNVersionNumber, {"name":"com.nutpi.rndemo","versionName":"1.0.0","versionCode":1000000}
7.5 应用截图

应用运行后,"应用版本信息"卡片中展示了三行数据:

字段
应用版本 1.0.0
构建版本 1000000
包名标识 com.nutpi.rndemo

应用运行截图


四、完整文件变更清单

文件路径 变更类型 说明
AwesomeProject/package.json 修改 新增 @react-native-ohos/react-native-version-number 依赖
AwesomeProject/App.tsx 修改 新增 VersionNumber 版本信息展示卡片
Myrndemo/entry/src/main/ets/version_number/RNVersionNumberPackage.ets 新增 RN Package 注册
Myrndemo/entry/src/main/ets/version_number/RNVersionNumberTurboModule.ts 新增 TurboModule 桥接
Myrndemo/entry/src/main/ets/version_number/Logger.ts 新增 日志工具
Myrndemo/entry/src/main/ets/RNPackagesFactory.ets 修改 注册 RNVersionNumberPackage
Myrndemo/entry/src/main/ets/generated/turboModules/RNVersionNumber.ts 新增 ETS TM 类型定义
Myrndemo/entry/src/main/ets/generated/turboModules/ts.ts 修改 新增 RNVersionNumber 导出
Myrndemo/entry/src/main/cpp/generated/RNVersionNumber.h 新增 C++ TurboModule 声明
Myrndemo/entry/src/main/cpp/generated/RNVersionNumber.cpp 新增 C++ TurboModule 实现
Myrndemo/entry/src/main/cpp/generated/RNOHGeneratedPackage.h 修改 新增 RNVersionNumber 注册
Myrndemo/entry/src/main/cpp/CMakeLists.txt 修改 新增 RNVersionNumber.cpp 源文件

五、关键决策说明

决策 1:选择 0.5.0-beta.1 版本

选择@react-native-ohos/react-native-version-number@0.5.0-beta.1

理由

  • 项目使用 RN 0.82.1 + RNOH 0.82.30,需要对应 0.82.x 的适配版本
  • 0.4.0 版本对应 RN 0.77.x,不支持 Autolink
  • 虽然 0.5.0-beta.1 是 beta 版,但功能简单(仅 getConstants()),风险可控

决策 2:源码集成 vs .har 依赖

选择:源码直接集成

理由

  • .har 路径依赖在不同构建环境下经常出现路径解析错误
  • 源码集成便于调试(可以直接在源码中打断点)
  • 仅 3 个文件,集成成本极低

决策 3:Codegen 桥接代码手动生成

选择:手动创建 ETS/C++ 桥接文件

理由

  • @react-native-ohos/react-native-version-numberNativeRNVersionNumber.ts 缺少 @codegenConfig 注解
  • Codegen 扫描不到该 Spec,无法自动生成桥接代码
  • 该 TurboModule 只有一个 getConstants() 方法,手动创建文件工作量极小

决策 4:getConstants 使用 ARK_METHOD_METADATA 而非 ARK_ASYNC_METHOD_METADATA

选择ARK_METHOD_METADATA(getConstants, 0)

理由

  • getConstants() 是同步方法,不返回 Promise
  • 在 JS 端的 index.js 中,TurboModuleRegistry.get('RNVersionNumber').getConstants() 是同步调用
  • 如果误用 ARK_ASYNC_METHOD_METADATAgetConstants() 会返回 Promise 而非对象,导致 JS 端解构失败

六、踩坑与排障记录

问题 1:Codegen 不生成 RNVersionNumber 桥接代码

现象:执行 react-native codegen-harmony 后只生成了 TTS 的文件

原因:npm 包中的 NativeRNVersionNumber.ts 没有使用 @codegenConfig 注解,Codegen 扫描不到

解决:手动创建 RNVersionNumber.ts(ETS 类型)、RNVersionNumber.h/.cpp(C++ 桥接),并更新 ts.tsRNOHGeneratedPackage.h

问题 2:ArkTS 编译报错 “has already exported a member named ‘RNVersionNumber’”

现象

Error Message: Module "./TTSNativeModule" has already exported a member named 'RNVersionNumber'. 
Consider explicitly re-exporting to resolve the ambiguity. 
At File: .../generated/turboModules/ts.ts:9:1

原因:在手动向 TTSNativeModule.ts 中添加 RNVersionNumber namespace 的同时,又创建了独立的 RNVersionNumber.ts 文件并在 ts.ts 中导出,导致同名导出冲突

解决:从 TTSNativeModule.ts 中移除 RNVersionNumber namespace,只保留独立的 RNVersionNumber.ts 文件

问题 3:import 路径需要从 ./generated/ts 改为 ../generated/ts

现象:ArkTS 编译报错找不到模块

原因:npm 包中的源码 import 路径 ./generated/ts 是相对于库内部结构的,复制到项目后目录层级不同

解决:修改 RNVersionNumberTurboModule.tsRNVersionNumberPackage.ets 中的 import 路径:

- import { TM } from "./generated/ts";
+ import { TM } from '../generated/ts';

七、数据流全景图

┌─────────────────────────────────────────────────────────────────┐
│                         JS 层 (App.tsx)                         │
│                                                                 │
│  import VersionNumber from 'react-native-version-number'        │
│        │                                                        │
│        │ Metro 重定向                                            │
│        ▼                                                        │
│  @react-native-ohos/react-native-version-number/index.js        │
│        │                                                        │
│        │ TurboModuleRegistry.get('RNVersionNumber')             │
│        │   .getConstants()                                      │
│        ▼                                                        │
├────────────────────────── C++ 层 ───────────────────────────────┤
│                                                                 │
│  RNOHGeneratedPackage                                           │
│    └─ createTurboModule("RNVersionNumber")                      │
│         │                                                       │
│         ▼                                                       │
│  RNVersionNumber (ArkTSTurboModule)                             │
│    └─ methodMap_: { getConstants → ARK_METHOD_METADATA }        │
│         │                                                       │
│         │ 调用 ArkTS 端                                          │
│         ▼                                                       │
├────────────────────────── ArkTS 层 ─────────────────────────────┤
│                                                                 │
│  RNVersionNumberTurboModule.getConstants()                      │
│    │                                                            │
│    │ bundleManager.getBundleInfoForSelfSync()                   │
│    ▼                                                            │
│  返回 { RNVersionNumber: { appVersion, buildVersion,            │
│                            bundleIdentifier } }                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

八、与 react-native-tts 集成的对比

维度 react-native-tts react-native-version-number
源码文件数 5 个 3 个
TurboModule 方法数 18 个 1 个 (getConstants)
C++ 方法映射 ARK_ASYNC_METHOD_METADATA 为主 ARK_METHOD_METADATA(同步)
Codegen 自动生成 ✅ 成功 ❌ 需手动
事件发射 ✅ 需要 emitDeviceEvent ❌ 无
第三方鸿蒙 API @kit.CoreSpeechKit @kit.AbilityKit
集成难度 ⭐⭐⭐⭐ ⭐⭐

九、总结

react-native-version-number 集成到 HarmonyOS NEXT 平台的核心流程可概括为 “源码复制 + 手动桥接”

  1. JS 端:安装 @react-native-ohos/react-native-version-number@0.5.0-beta.1,Metro 自动重定向 import
  2. ArkTS 端:复制 3 个源文件,修改 import 路径,实现 getConstants() 调用 bundleManager
  3. C++ 端:手动创建 RNVersionNumber.h/.cpp,注册 getConstants 方法映射,更新 RNOHGeneratedPackage.h

最关键的三个注意事项:

  • 版本对齐:必须选择与 RN 版本匹配的鸿蒙适配版本(0.82.x → 0.5.0-beta.1)
  • Codegen 可能不自动生成:如果 Spec 缺少 @codegenConfig 注解,需手动创建桥接文件
  • 同步 vs 异步方法getConstants() 是同步方法,C++ 侧必须使用 ARK_METHOD_METADATA,不能用 ARK_ASYNC_METHOD_METADATA

参考文档

Logo

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

更多推荐