react_native笔记

2025/05/18

Tags: react native c++ turbo module

Table of Contents

qt qml 中 可以把 c++对象注入 qml engine

reac native 自从收编 nitro 之后,也获得了这种能力

我已经用qt qml 在windows/linux/mac / embed linux中写gui, 但对于android和ios 一直没有实践

1是确实没有什么项目, 2是lgpl 和商城规则或多或少存在争议

所以在知道react native也可以这么说后,我打算开始玩一玩, 尝试把我自己写的c++ 静态反射库应用于其中

于是有此笔记,将和往常一样不定时整理更新

1 配置sdk版本,及兼容的最低版本

1.1 IOS

1.1.1 SDK版本选择

sdk向下兼容是有限制的,所以想开发兼容很旧的ios系统的程序,只能下载旧的xcode版本开发,且开发后上不了架,并且ios系统不一定能运行旧版本的xcode, 想开发不上架的ios app,有可能需要装虚拟机,安装旧的系统,再安装旧的xcode,再开发

xcode 版本支持的sdk版本支持参考这里

因为apple store要求sdk需要使用较新版本,所以想上架的话, 最好在apple store安装最新的xcode,更新最新的sdk

apple store 安装完的xcode 第一次打开时可能有弹出一个选择安装sdk的版本,如果有的话把ios sdk 打钩安装

下载的xcode.xip解压的好像没有这个弹窗 ,有的话也是把ios sdk打钩安装(这试了几次都没有)

选择了xcode就等于选择了sdk 版本了,想改只能切换xcode版本

附上一些在命令行中切换 xcode和sdk版本的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#选择xcode
xcode-select -p
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
xcodebuild -showsdks

#查看可用sdk
xcodebuild -showsdks
ls `xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/

#查看当前sdk
  xcrun --show-sdk-path 
xcrun --sdk macosx --show-sdk-version



#临时配置sdk
export SDKROOT=`xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk

#编译时配置sdk
cd build && xcodebuild -sdk macosx13.3
#or
#cmake 
cmake .. -DCMAKE_OSX_SYSROOT=`xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk

#GN
gn gen out/Default --args='mac_sdk_path="`xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk"'

1.1.2 向下兼容至何种版本

向下兼容至何种版本,则需要开发者指定, 且只能在xcode 支持的版本范围中选 择参考这里

有几个位置需要修改 ,以xcode 16为例,它支持 ios 16.0 - 18.2的开发, 我想兼容到16.0

…/react_active_src/ios/Podfile中的

1
platform :ios, 16.0 #安装哪种sdk的依赖库

如果已经安装过pods了,需要删除一下,重新安装 删除Podfile.lock ,还有pods目录,重新安装依赖

1
2
3
rm -rf .../react_active_src/ios/pods
rm .../react_active_src/ios/Podfile.lock
cd .../react_active_src/ios/ && pod install 

xcode打开工程设置

image-20250503144615711

如果是真机,直接选择,不用安装simulator, 如果是simulator, 提交安装想兼容的最低版本 , 最新版本的simualtor也需要安装,否则选择不了其他低版本的simulator , 图上图我选中iPhone 14 pro,但安装了simualtor 16.0和 18.2才可以选择16.0

general 页设置minimum deployments

image-20250503142804878

build setting

image-20250503142738319

显示为灰色的ios 18.2是目前的xcode最新的sdk, 不管选中的minimum deployments是什么版本, 都是用最新的sdk编译的

1.2 Andorid

react native 版本影响 JDK版本, 和 Gradle 包管理器的版本 , 它们只是参与了工程构建,不影响app 上架, 根据具体使用的react native版本按需要选择

1.2.1 SDK版本选择

指定版本需要修改

…/react_active_src/android/build.gradle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
buildscript {
  //额外属性 extra
    ext {
        buildToolsVersion = "35.0.0"
        minSdkVersion = 24
        compileSdkVersion = 35
        targetSdkVersion = 35
        ndkVersion = "27.1.12297006"
        kotlinVersion = "2.0.21"
    }
  //源
    repositories {  
        google()
        mavenCentral()
    }
  //依赖
    dependencies {
        classpath("com.android.tools.build:gradle")
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
    }
}

apply plugin: "com.facebook.react.rootproject"

不同的目录层级可能会有多个build.gradle, 但版本相关的设置都会引用根目录的build.gradle中ext中指定的版本

1
2
3
4
5
6
7
8
9
AwesomeProject/android on  main [!?] via 🅶 v8.13 via ☕ v17.0.15 took 17m22s
❯ grep -irn "rootproject" .
./app/build.gradle:76:    ndkVersion rootProject.ext.ndkVersion
./app/build.gradle:77:    buildToolsVersion rootProject.ext.buildToolsVersion
./app/build.gradle:78:    compileSdk rootProject.ext.compileSdkVersion
./app/build.gradle:83:        minSdkVersion rootProject.ext.minSdkVersion
./app/build.gradle:84:        targetSdkVersion rootProject.ext.targetSdkVersion
./build.gradle:21:apply plugin: "com.facebook.react.rootproject"
./settings.gradle:4:rootProject.name = 'AwesomeProject'

和ios一样, 如果想上架的话,google play也sdk版本有要求

1.2.1 向下兼容至何种版本

andorid 版本, sdk版本, ndk版本, 搭配大致可以参照下表

compileSdkVersion Android API 推荐 NDK 版本 支持的 NDK 功能 支持的架构
34 Android 14 r26 ~ r27b 64位架构,arm64-v8a,逐渐放弃旧架构 arm64-v8a, armeabi-v7a, x86_64
33 Android 13 r23b ~ r26b 优化的性能、稳定性支持,支持 Android 12 及以上 arm64-v8a, armeabi-v7a, x86_64
32 Android 12L r22b ~ r25c 支持 Android 12,API 30,逐渐加入新的功能 arm64-v8a, armeabi-v7a, x86_64
31 Android 12 r21e ~ r23b 启用 64 位架构支持,NDK 特性基本稳定 arm64-v8a, armeabi-v7a
30 Android 11 r20b ~ r22b 完全支持 arm64-v8a, armeabi-v7a,向下兼容 arm64-v8a, armeabi-v7a
29 Android 10 r19c ~ r21b 默认支持 armeabi-v7a, arm64-v8a, 32 位/64 位 armeabi-v7a, arm64-v8a
28 Android 9 r18b ~ r20b 启动 64 位架构支持,目标为 armeabi-v7a armeabi-v7a
27 以下 Android 8.x r17b ~ r19b 不支持新的架构,只支持 armeabi-v7a armeabi-v7a

截止2025年5月3日 , react native 0.79.2 的模板版本配置为

1
2
3
4
5
6
        buildToolsVersion = "35.0.0"
        minSdkVersion = 24
        compileSdkVersion = 35
        targetSdkVersion = 35
        ndkVersion = "27.1.12297006"
        kotlinVersion = "2.0.21"

如果不考虑ndk的话,这个版本已经覆盖市场上95%的手机了, 建议开发新的app就用这个配置

而使用24的ndk版本,则说明 使用android 12以下的系统api有不兼容的风险,尽量避免不稳定的api 的使用即可

由于我主要使用c++ 和 react native进行开发,所以kotlin版本我比较不在意,暂时不探索

2. 在app中添加多个Turbo C++ 模块

个安装环境参考 环境设置

官方文档推荐的作法是使用一个framework 如 expo 进行开发

但听很多吐槽这个框架性能问题的声音,我还是更倾向于不使用framework

于是我找到这个文档getting-started-without-a-framework

在这个基础上, 参考native开发文档探索加入c++ 进行编译

2.1 用Ts生成跨平台胶水代码

进入工程根目录,创建目录 specs 用于存放ts接口 ,然后创建两个接口

名称必需以Native开头,否则不能正常生成c++胶水代码

两个文件内容如下, 最后一行的注册名称建议和文件名保持一致, Spec按需改,后面会成为一些胶水类的名称后辍

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//NativeViewModelA.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
  readonly getStr: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeViewModelA');


//NativeViewModelB.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
  readonly getStr: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeViewModelB');

然后修改package.json,添加codegen 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ....
"codegenConfig": {
     "name": "ViewmodelsSpecs",
     "type": "modules",
     "jsSrcsDir": "specs",
     "android": {
         "javaPackageName": "com.viewmodels.specs"
     }
   },
//....

2.1.1 Android

接着执行下面的命令 生胶水代码

1
yarn android

如果是android 生成的代码在 …/root/android/app/build

1
2
3
4
5
generated/
| source/
| | codegen/
| | | java/
| | | jni/

即生成了java接口,也生成了jni 中的c++接口

查了一下,好像目前没办法把Java生成的步骤省略掉,不过生成了不去用它也没有问题

不只有接口定义,还有一些胶水代码, c++ 还有一个cmakelists.txt,后面需要配置到构建系统参与构建

2.1.2 IOS

如果是ios 生成的代码在 ios/build

1
2
generated/
| ios/

2.2 理解生成的c++胶水

2.1 中生成的.cpp和.h文件,不是所有都和我们最开始定义的typescript 的接口有直接的关系,

我们先只了解下面这三个类就足够

它们都在ViewmodelsSpecsJsI.h头文件里 (加粗部分为package.json,codegenconfig节点的的name)

后继在写c++实现的的时候,只需要include 这个头文件,继承_CxxSpec类,实现接口即可

_CxxSpecJSI 和 _CxxSpec 为后辍的类,都继参自 TurboModule

Delegate继承了_CxxSpecJSI,也间接继承自TurboModule类

2.2.1 _CxxSpecJSI类

_CxxSpecJSI 只是定义 Ts中的定义的module的方法(没有实现)

以及一个构造函数用于传递TurboModule类构造函数参数jsInvoker ,以便以在c++中执行js

2.2.2 _CxxSpec类

_CxxSpec 这个类重载了 TurboModule的create和getPropertiyNames两个方法

应该在是jsx用直接访问属性用的,暂时没看到具体实现,应该是用报turboModule的默认实现了

以及一个根据配置的名称生成的静态的字符字段

2.2.3 Delegate 类

CxxSpec 的私有友元类Delegate,继承了_CxxSpecJSI , 实现了Ts中定义的那些方法的包装, 主要做参数校验, 以及返回Js绑定给jsx

2.3 实现编码, 添加构建任务

创建shared目录, 创建

头文件 include ViewmodelsSpecsJsI.h 后

NativeViewModelA 继承 NativeViewModelACxxSpec类

NativeViewModelB 继承 NativeViewModelBCxxSpec类

2.2中已经说过了, CxxSpec类中对应 Ts中定义中的方法 会在其私有友元类Delegate中被包装,进行简单的参数个数校验,以及返回到 Jsx 中

所以在实现时不需要考虑这些东西

2.3.1 实现编码

这个步骤 ios 和android都需要,这些代码都创建在shared目录

//log.h 用于在ios和andorid上用一致的宏记录日志

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef HBBLOG
#define HBBLOG

#if __ANDROID__
  #include <android/log.h>
  #define HBBLOG_TAG "TurboModule"
  #define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, HBBLOG_TAG, __VA_ARGS__)
  #define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, HBBLOG_TAG, __VA_ARGS__)

#elif __APPLE__
  #include <TargetConditionals.h>
  #if TARGET_OS_IPHONE

    #ifdef __OBJC__
      // 只有在 Objective-C/Objective-C++ 编译器中才允许用 NSLog
      #import <Foundation/Foundation.h>
      #define LOG_INFO(...) NSLog(__VA_ARGS__)
      #define LOG_ERROR(...) NSLog(__VA_ARGS__)
    #else
      // 纯 C++ 模式时不允许 NSString/@
      #define LOG_INFO(...) printf(__VA_ARGS__)
      #define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
    #endif

  #else
    #define LOG_INFO(...) printf(__VA_ARGS__)
    #define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
  #endif

#else
  #define LOG_INFO(...) printf(__VA_ARGS__)
  #define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
#endif

#endif // HBBLOG



// example
//LOG_INFO("Init success: %d", 42);
//LOG_ERROR("Error code: %s", errMessage.c_str());
//LOG_INFO("Initializing YourModule");

//NativeViewModelA.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include "ViewmodelsSpecsJSI.h"
#include <memory>
#include <string>

namespace facebook::react {
class JSI_EXPORT NativeViewModelA :public NativeViewModelACxxSpec<NativeViewModelA>
{
  
  public :
  NativeViewModelA(std::shared_ptr<CallInvoker> jsInvoker);
  jsi::String getStr(jsi::Runtime &rt, jsi::String input);
  
};

}
// namespace facebook::react

//NativeViewModelA.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "NativeViewModelA.h"
#include "log.h"

namespace facebook::react {

NativeViewModelA::NativeViewModelA(std::shared_ptr<CallInvoker> jsInvoker)
: NativeViewModelACxxSpec(std::move(jsInvoker)) {}

jsi::String NativeViewModelA::getStr(jsi::Runtime &rt, jsi::String input)
{
  LOG_ERROR("get string with native view model A");
  return input;
}

} // namespace facebook::react

//NativeViewModelB.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include "ViewmodelsSpecsJSI.h"
#include <memory>
#include <string>

namespace facebook::react {
class JSI_EXPORT NativeViewModelB :public NativeViewModelBCxxSpec<NativeViewModelB>
{
  
  public :
  NativeViewModelB(std::shared_ptr<CallInvoker> jsInvoker);
  jsi::String getStr(jsi::Runtime &rt, jsi::String input);
  
};

}
// namespace facebook::react

//NativeViewModelB.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "NativeViewModelB.h"
#include "log.h"

namespace facebook::react {

NativeViewModelB::NativeViewModelB(std::shared_ptr<CallInvoker> jsInvoker)
: NativeViewModelBCxxSpec(std::move(jsInvoker)) {}

jsi::String NativeViewModelB::getStr(jsi::Runtime &rt, jsi::String input)
{
  LOG_ERROR("get string with native view model B");
  return input;
}

} // namespace facebook::react

2.3.2 Android

2.3.2.1 添加到构建系统

android使用gradle 添加额外的cmake 步骤来编译c++代码

在app 根目执行下面命令,创建一个android jni 目录

1
android/app/src/main/jni

创建app_root/android/app/src/main/jni/CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
cmake_minimum_required(VERSION 3.13)

# Define the library name here.
project(appmodules)

# This file includes all the necessary to let you build your React Native application
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)

# Define where the additional source code lives. We need to crawl back the jni, main, src, app, android folders
target_sources(${CMAKE_PROJECT_NAME} PRIVATE 
../../../../../shared/NativeViewModelA.cpp
../../../../../shared/NativeViewModelB.cpp
)

# Define where CMake can find the additional header files. We need to crawl back the jni, main, src, app, android folders
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)

target_link_libraries(${CMAKE_PROJECT_NAME}  -llog)

添加到 app_root/android/app/build.gradle 的android结点下

1
2
3
4
5
6
7
8
9
...
android{
...
   externalNativeBuild {
       cmake {
           path "src/main/jni/CMakeLists.txt"
       }
   }
}

2.3.2.1 创建provider

下载onLoad.cpp ,以它为模板修改Jsx请求c++对象时的创建实例逻辑,可以在这里管理c++实例的生命周期

1
cd android/app/src/main/jni && curl -O https://raw.githubusercontent.com/facebook/react-native/v0.76.0/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp

修改onLoad.cpp

#onLoad.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...

#include <NativeViewModelA.h>
#include <NativeViewModelB.h>

...


std::shared_ptr<TurboModule> cxxModuleProvider(
    const std::string& name,
    const std::shared_ptr<CallInvoker>& jsInvoker) {
   if (name == NativeViewModelA::kModuleName) {
     return std::make_shared<NativeViewModelA>(jsInvoker);
   }
   else if (name == NativeViewModelB::kModuleName) {
     return std::make_shared<NativeViewModelB>(jsInvoker);
   }

  // And we fallback to the CXX module providers autolinked
  return autolinking_cxxModuleProvider(name, jsInvoker);
}

...

编译运行

使用andorid sutido 运行,可以在logcat里看到调用时打印的日志

2.3.3 ISO

2.3.3.1 添加到构建系统

ios使用 xcodeproj 编译c++代码

image-20250505161210039

image-20250505161305112

image-20250505161333407

添加Provider类,用来加载模块

image-20250505165040375

2.3.3.3 创建provider

每个npm库应该有一个provider类,当jsx请求module时,为jsx创建实例

在package.json中的codegen结点再加一个ios子节点

1
2
3
4
5
6
     "ios": {
        "modulesProvider": {
          "NativeViewModelA":  "ViewmodelsProvider",
          "NativeViewModelB":  "ViewmodelsProvider"
        }
     }

可以自己控件实例的生命周期

选中项目,按 command + N

image-20250518144423423

名称和package.json 中的 codegenconfig节点的的name 字段一样,再加provider后辍就行, 稍后还需要在codegen节点中添加ios provider

//package.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  "codegenConfig": {
      "name": "ViewmodelsSpecs",
      "type": "modules",
      "jsSrcsDir": "specs",
      "android": {
          "javaPackageName": "com.viewmodels.specs"
      },
     "ios": {
        "modulesProvider": {
          "NativeViewModelA":  "ViewmodelsProvider",
          "NativeViewModelB":  "ViewmodelsProvider"
        }
     }

  },

2.4 测试

修改app.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import React from 'react';
import {
  Button,
  SafeAreaView,
  StyleSheet,
  Text,
  TextInput,
  View,
} from 'react-native';
import ViewModelA from './specs/NativeViewModelA';
import ViewModelB from './specs/NativeViewModelB';

function App(): React.JSX.Element {
  //const [value, setValue] = React.useState('');
  //const [reversedValue, setReversedValue] = React.useState('');
  //const onPress = () => {
  //  const revString = SampleTurboModule.reverseString(value);
  //  setReversedValue(revString);
  //};

  const [value1, setValue1] = React.useState('');
  const [str1Value, setStr1Value] = React.useState('');
  const onPress1 = () => {
    const str1 = ViewModelA.getStr(value1);
    setStr1Value(str1);
  };


  const [value2, setValue2] = React.useState('');
  const [str2Value, setStr2Value] = React.useState('');
  const onPress2 = () => {
    const str2 = ViewModelB.getStr(value2);
    setStr2Value(str2);
  };


  return (
    <SafeAreaView style={styles.container}>
      <View>
        <Text style={styles.title}>
          Welcome to C++ Turbo Native Module Example
        </Text>
        <Text>Write down here he text you want to revert</Text>


        <TextInput
          style={styles.textInput}
          placeholder="Write your text here"
          onChangeText={setValue1}
          value={value1}
        />
        <Button title="setstr1" onPress={onPress1} />
        <Text>Reversed text: {str1Value}</Text>

        <TextInput
          style={styles.textInput}
          placeholder="Write your text here"
          onChangeText={setValue2}
          value={value2}
        />
        <Button title="setstr1" onPress={onPress2} />
        <Text>Reversed text: {str2Value}</Text>

      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
    marginBottom: 20,
  },
  textInput: {
    borderColor: 'black',
    borderWidth: 1,
    borderRadius: 5,
    padding: 10,
    marginTop: 10,
  },
});

export default App;

用android stuido 或xcode 调试时,可以看到执行c++代码时的打印

>> Home

Comments