diff --git a/_sidebar.md b/_sidebar.md index 30374cf5ce3b9bd422984b92555620bd4a3fe039..2f0b084eaa38bc1a7c63927f302aa9facd8bcd8c 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -11,6 +11,8 @@ - [新架构介绍](/zh-cn/new-architecture.md) - [TurboModules](/zh-cn/turbomodule.md) - [Fabric 组件](/zh-cn/fabric.md) + - [使用 C-API 开发 Fabric 组件](/zh-cn/fabric-capi.md) + - [使用 ArkTS API 开发 Fabric 组件](/zh-cn/fabric-arkts.md) - [迁移三方库到新架构](/zh-cn/migration.md) - 其他 diff --git a/zh-cn/deprecated/fabric.md b/zh-cn/deprecated/fabric.md new file mode 100644 index 0000000000000000000000000000000000000000..b4c0629c455eeecfa781334b1e6fca3bc82bab2b --- /dev/null +++ b/zh-cn/deprecated/fabric.md @@ -0,0 +1,878 @@ +> [!WARNING] 本文档为非公开文档,仅用于三方库使用和开发指导,不涉及任何 React Native OpenHarmony 框架的信息,且会随着 React Native OpenHarmony 框架持续迭代更新,当前版本不代表最终展示版本。 + +# Fabric 组件 + +Fabric 组件是一种使用 Fabric 渲染器渲染并展示在屏幕上的 UI 组件。 + +![fabric](../img/fabric.png) + +在开发 Fabric 组件前,需要先创建一个 JavaScript 接口描述文件。之后 Codegen 会根据这个文件创建一些 C++ 脚手架代码,用于将部分组件逻辑(比如调用原生平台接口能力)与 React Native 结合起来。C++ 代码在各个平台都是一样的,只要组件能够与生成的 C++ 代码连接起来,就可以导入到 App 并运行。 + +## 如何创建 Fabric 组件 + +若要创建一个 Fabric 组件,需要遵循以下步骤: + +1. 声明 JavaScript 接口; +2. 配置组件以用于 Codegen 生成统一代码,生成的代码可添加为 App 的依赖; +3. 编写所需的原生代码。 + +接下来会创建一个简单的名为 `RTNCenteredText` 的 Fabric 组件作为示例。 + +**该示例仅提供 HarmonyOS 版本,Android/iOS 版本请阅读 React-Native 官方文档的 [Fabric 组件章节](https://reactnative.cn/docs/the-new-architecture/pillars-fabric-components)** + +### 1. 目录配置 + +同样的,我们按照一般的三方库目录结构来配置: + +``` +. +├── MyApp +└── RTNCenteredText + ├── android(Android 的原生实现代码,若有) + ├── ios(iOS 的原生实现代码,若有) + ├── harmony(HarmonyOS 的原生实现代码) + └── src (js/ts代码) +``` + +### 2. 声明 JavaScript 接口 + +新架构要求必须使用强类型风格语言声明 JavaScript 接口(Flow 和 TypeScript 皆可)。Codegen 会根据这些接口声明来生成强类型的语言,其中包括 C++、Objective-C 和 Java。 + +对于声明类型的代码文件必须满足以下两点要求: + +1. 文件必须使用 `NativeComponent` 命名,在使用 Flow 时,以 `.js` 或 `.jsx` 为后缀名;在使用 Typescript 时,以 `.ts` 或 `.tsx` 为后缀名。Codegen 只会找到匹配这些命名规则的文件; + +2. 代码中必须要输出 HostComponent 对象。 + +以下是使用 Flow 和 TypeScript 声明的 RTNCenteredText 组件。在 `src` 目录中,创建一个命名为 `RTNCenteredText` 并带有相应后缀名的文件。 + + + +#### **flow** + +RTNCenteredTextNativeComponent.js + +```js +// @flow strict-local + +import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes"; +import type { HostComponent } from "react-native"; +import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + text: ?string, + // 在这里添加其他属性 +|}>; + +export default (codegenNativeComponent( + "RTNCenteredText" +): HostComponent); +``` + +#### **typescript** + +RTNCenteredTextNativeComponent.ts + +```ts +import type { ViewProps } from "ViewPropTypes"; +import type { HostComponent } from "react-native"; +import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; + +export interface NativeProps extends ViewProps { + text?: string; + // 添加其它 props +} + +export default codegenNativeComponent( + "RTNCenteredText" +) as HostComponent; +``` + + + +在声明文件的顶部导入了一些内容。以下是开发 Fabric 组件必须要导入的内容: + +- `HostComponent` 类型: 导出的组件需要与这个类型保持一致; +- `codegenNativeComponent` 函数:负责将组件注册到 JavaScript 运行时。 + 声明文件的中间部分包含了组件的 props。Props("properties" 的缩写)是用于自定义 React 组件的参数信息。在本例中,需要控制组件的 text 属性。 + +在声明文件的最后部分,导出了泛型函数 `codegenNativeComponent` 的返回值,此函数需要传递组件的名称。 + +### 3. Codegen 配置 + +#### 3.1 配置 `package.json` 文件 + +请在 `RTNCenteredText` 的根目录创建 `package.json` 文件。 + +```json +{ + "name": "rtn-centered-text", + "version": "0.0.1", + "description": "Showcase a Fabric component with a centered text", + "react-native": "src/index", + "source": "src/index", + "files": [ + "src", + "harmony", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "harmony"], + "repository": "https://github.com//rtn-centered-text", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rtn-centered-text/issues" + }, + "homepage": "https://github.com//rtn-centered-text#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + } +} +``` + +#### 3.2 选择 Fabric 的原生实现方式,配置 codegen + +RNOH 有特殊的架构限制,需要开发者在开发前根据需求选择好使用 ArkTS API 还是 CAPI 实现 Fabric。 + +关于如何选择,请阅读这篇[说明文档](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/capi-architecture.md)。 + +此步操作是要将 Codegen 的配置声明到 harmony.codegenConfig 字段。 + +- version: 枚举值,1 代表ArkTS组件的codegen版本,2 代表CAPI组件的codegen版本;(关于ArkTS和CAPI组件请见2.2的详细说明) +- specPaths:用于找到 js 接口声明文件的相对路径,它将被 Codegen 解析 + +> [!WARNING] 接入 codegen 之后,同一个模块中 ArkTS 版本和 CAPI 版本的 Fabric 无法共存,请先选择好实现方式 + +在 `package.json` 中新增 harmony.codegenConfig 字段: + +##### Option1: ArkTS API 实现 Fabric + +```json +{ + // ... + "harmony": { + "codegenConfig": { + "version": 1, + "specPaths": ["./src"] + } + } +} +``` + +##### Option2: C-API 实现 Fabric + +```json +{ + // ... + "harmony": { + "codegenConfig": { + "version": 2, + "specPaths": ["./src"] + } + } +} +``` + +#### 3.3 codegen通用配置项 + +HarmonyOS 需要在 RN 工程中通过运行脚本来执行 Codegen。 + +打开 RN 工程下的 package.json,如 `MyApp/package.json`,添加: + +```json +{ + ... + "scripts": { + ... + "codegen": "react-native codegen-harmony --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony" + }, + ... +} +``` + +> codegen-harmony 参数介绍: + +1. --rnoh-module-path: 指定 @rnoh/react-native-openharmony 模块的相对路径,用于存储生成的 ts 文件;如果使用 har 包引入 RNOH SDK,则需要指向安装之后的路径,比如:./harmony/entry/oh_modules/@rnoh/react-native-openharmony" + +2. --cpp-output-path: 指定用于存储生成的 C++ 文件的输出目录的相对路径,默认 ./harmony/entry/src/main/cpp/generated; + +3. --project-root-path: 包根目录的相对路径。 + +#### 3.4 codegen执行,生成胶水代码 + +首先,需要将包含模块的 NPM 包添加到 App。请确保 package.json 已经配置安装好以下依赖: + +```json +{ + ... + "dependencies": { + "react-native-harmony": "x.x.x", + ... + }, + "overrides": { + "@react-native/codegen": "0.74.0" + }, + ... +} +``` + +执行以下操作,假设 MyApp 为您的 App 工程路径 + +```bash +// 进入模块工程 +cd RTNCenteredText + +// 打包模块 +npm pack + +// 进入 App 工程 +cd ../MyApp + +// 本地路径安装模块 +npm i file:../RTNCenteredText/rtn-centered-text-0.0.1.tgz + +// 执行以下命令执行 codegen (HarmonyOS only) + +npm run codegen + +``` + +此命令会将 RTNCenteredText 模块添加到 App 内的 node_modules 目录。 + +执行codegen命令后在harmony工程得到如下目录结构 +```md +harmony\entry\src\main\cpp\generated +├──RNOHGeneratedPackage.h +└──rtn_centered_text + ├──react + │ └──renderer + │ └──components + │ └──rtn_centered_text + │ ├──ComponentDescriptors.h + │ ├──EventEmitters.cpp + │ ├──EventEmitters.h + │ ├──Props.cpp + │ ├──Props.h + │ ├──ShadowNodes.cpp + │ ├──ShadowNodes.h + │ ├──States.cpp + │ └──States.h + │ + └──RNOH + └──generated + ├──BaseRtnCenteredTextPackage.h + └──components + ├──BaseRTNCenteredTextComponentInstance.h + └──RTNCenteredTextJSIBinder.h +``` + +// TODO 需添加各文件作用描述 + +### 4. 实现原生组件 + +#### Option1: 使用 ArkTS API 实现原生组件 + +HarmonyOS 平台中 ArkTS 版本的 Fabric 组件的原生代码必须包含以下三个部分: + +1. 创建用于实现组件的 RTNCenteredText.ets +2. 创建 RTNCenteredTextPackage.ts +3. 创建用于导出模块的 index.ets 和 ts.ts +4. 修改 oh-package.json5,hvigorfile.ts,module.json5 + +> [!TIP] 可以在 DevEco Studio 中通过 File -> New -> Module.. -> Static Lirbrary 创建空壳模块,以此为基础修改文件内容 + +HarmonyOS 原生代码文件结构应为如下: + +```md +harmony +└── rtn_centered_text + ├── src + │ └── main + │ ├──ets + | | ├── RTNCenteredTextPackage.ts + │ │ └── RTNCenteredText.ets + │ └── module.json5 + ├── build-profile.json5 + ├── hvigorfile.ts + ├── index.ets + ├── oh-package.json5 + └── ts.ts +``` + + + +#### **RTNCenteredText.ets** + +```ts +import { RNComponentContext, RNViewBase } from '@rnoh/react-native-openharmony'; +// import codegen 生成的内容 +import { RNC } from "@rnoh/react-native-openharmony/generated"; + +@Component +export struct RTNCenteredText { + public static readonly NAME = RNC.RTNCenteredText.NAME + public ctx!: RNComponentContext + public tag: number = 0 + @State private descriptorWrapper: RNC.RTNCenteredText.DescriptorWrapper = {} as RNC.RTNCenteredText.DescriptorWrapper + private eventEmitter: RNC.RTNCenteredText.EventEmitter | undefined = undefined + private cleanUpCallbacks: (() => void)[] = [] + + aboutToAppear() { + this.eventEmitter = new RNC.RTNCenteredText.EventEmitter(this.ctx.rnInstance, this.tag) + this.onDescriptorWrapperChange(this.ctx.descriptorRegistry.findDescriptorWrapperByTag(this.tag)!) + this.cleanUpCallbacks.push(this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, + (_descriptor, newDescriptorWrapper) => { + this.onDescriptorWrapperChange(newDescriptorWrapper! as RNC.RTNCenteredText.DescriptorWrapper) + // 组件属性更新时进入的回调 + } + )) + } + + private onDescriptorWrapperChange(descriptorWrapper: RNC.RTNCenteredText.DescriptorWrapper) { + this.descriptorWrapper = descriptorWrapper + } + + aboutToDisappear() { + this.cleanUpCallbacks.forEach(cb => cb()) + } + + build() { + RNViewBase({ ctx: this.ctx, tag: this.tag }) { + Text(this.descriptorWrapper.props.text) + .height("100%") + .width("100%") + .fontSize(30) + .textAlign(TextAlign.Center) + } + } +} +``` + + + + + +#### **RTNCenteredPackage.ts** + +```ts +import { RNPackage } from "@rnoh/react-native-openharmony/ts"; +import type { + DescriptorWrapperFactoryByDescriptorTypeCtx, + DescriptorWrapperFactoryByDescriptorType, +} from "@rnoh/react-native-openharmony/ts"; +// import codegen 生成的内容 +import { RNC } from "@rnoh/react-native-openharmony/generated/ts"; + +export class RTNCenteredTextPackage extends RNPackage { + createDescriptorWrapperFactoryByDescriptorType( + ctx: DescriptorWrapperFactoryByDescriptorTypeCtx + ): DescriptorWrapperFactoryByDescriptorType { + return { + [RNC.RTNCenteredText.NAME]: (ctx) => + new RNC.RTNCenteredText.DescriptorWrapper(ctx.descriptor), + }; + } +} +``` + + + +创建 `ts.ts` + + + +#### **ts.ts** + +```ts +export * from "./src/main/ets/RTNCenteredTextPackage"; +``` + + + +创建 `index.ets` + + + +#### **index.ets** + +```ts +export * from "./ts"; +export * from "./src/main/ets/RTNCenteredText"; +``` + + + +#### Option2: 使用 C-API 实现原生组件 + +HarmonyOS 平台中 C-API 版本的 Fabric 组件的原生代码必须包含以下部分: + +1. 创建用于实现组件的 RTNCenteredTextComponentInstance.h、RTNCenteredTextComponentInstance.cpp; +2. 创建用于对接 ArkUI 的 xxxNode.h、xxxNode.cpp;(若框架已经实现相关的 Node,此步可以跳过。本例中需要用到的 TextNode、StackNode 框架已经实现,故无需自行创建) +3. Cpp 脚手架(请看 codegen 章节) +4. 修改 oh-package.json5,hvigorfile.ts,module.json5,build-profile.json5 + +> [!TIP] 可以在 DevEco Studio 中通过 File -> New -> Module.. -> Static Lirbrary 创建空壳模块,以此为基础修改文件内容 + +HarmonyOS 原生代码文件结构应为如下: + +```md +harmony +└── rtn_centered_text + ├── src + │ └── main + │ ├── cpp + │ │ ├── CMakeLists.txt + │ │ ├── CenteredTextPackage.h + │ │ ├── RTNCenteredTextComponentInstance.h + │ │ └── RTNCenteredTextComponentInstance.cpp + │ └── modules.json5 + ├── build-profile.json5 + ├── hvigorfile.ts + └── oh-package.json5 +``` + +创建 `RTNCenteredTextComponentInstance.h` + + + +#### **RTNCenteredTextComponentInstance.h** + +```cpp +#pragma once +#include "RNOH/generated/components/BaseRTNCenteredTextComponentInstance.h" +#include "RNOH/arkui/StackNode.h" +#include "RNOH/arkui/TextNode.h" + +namespace rnoh { +class RTNCenteredTextComponentInstance : public BaseRTNCenteredTextComponentInstance { +private: + using FragmentTouchTargetByTag = std::unordered_map>; + + using Super = BaseRTNCenteredTextComponentInstance; + TextNode m_textNode{}; + StackNode m_stackNode{}; + +public: + RTNCenteredTextComponentInstance(Context context); + StackNode &getLocalRootArkUINode() override; + +protected: + void onPropsChanged(SharedConcreteProps const &props) override; +}; +} // namespace rnoh +``` +这里的BaseRTNCenteredTextComponentInstance.h位于entry/src/main/cpp/generated/components目录下,通过后续的codegen命令自动生成。 + + +方法介绍: + +onPropsChanged:当 React Native 端的组件属性发生变化时(例如,文本内容、样式等),这个变化会通过桥接层传递到原生端。onPropsChanged 方法就是用来接收这些变化并作出相应处理的。 + +创建 `RTNCenteredTextComponentInstance.cpp` + + + +#### **RTNCenteredTextComponentInstance.cpp** + +```cpp +#include "RTNCenteredTextComponentInstance.h" + +namespace rnoh { + +RTNCenteredTextComponentInstance::RTNCenteredTextComponentInstance(Context context) + : Super(std::move(context)) { + m_stackNode.insertChild(m_textNode, 0); +} + +StackNode &RTNCenteredTextComponentInstance::getLocalRootArkUINode() { return m_stackNode; } + +void RTNCenteredTextComponentInstance::onPropsChanged(SharedConcreteProps const &props) { + CppComponentInstance::onPropsChanged(props); + if (props == nullptr) { + return; + } + m_textNode.setTextContent(props->text); + m_textNode.setFontSize(30.0); + m_textNode.setAlignment(ARKUI_ALIGNMENT_CENTER); +} + +} // namespace rnoh +``` + + + +修改 `RTNCenteredTextPackage.h` + + + +#### **RTNCenteredTextPackage.h** + +```cpp +#include "RNOH/Package.h" +#include "RNOH/generated/BaseRtnCenteredTextPackage.h" +#include "RTNCenteredTextComponentInstance.h" + +using namespace rnoh; +using namespace facebook; + +namespace rnoh { + +class RTNCenteredTextPackage : public BaseRtnCenteredTextPackage { +private: + using Super = BaseRtnCenteredTextPackage; +public: + RTNCenteredTextPackage(Package::Context ctx): BaseRtnCenteredTextPackage(ctx) {} + + ComponentInstance::Shared createComponentInstance(const ComponentInstance::Context& ctx) { + if (ctx.componentName == "RTNCenteredText") { + return std::make_shared(ctx); + } + return nullptr; + }; + +}; +} // namespace rnoh +``` + + + +修改 `CMakeLists.txt` + + + +#### **CMakeLists.txt** + +```txt +set(rtn_centered_text_generated_dir "${RNOH_GENERATED_DIR}/rtn_centered_text") + +file(GLOB_RECURSE rtn_centered_text_generated_SRC "${rtn_centered_text_generated_dir}/**/*.cpp") + +file(GLOB rtn_centered_text_SRC CONFIGURE_DEPENDS *.cpp) + +add_library(rtn_centered_text SHARED ${rtn_centered_text_SRC} ${rtn_centered_text_generated_SRC}) + +target_include_directories(rtn_centered_text PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${rtn_centered_text_generated_dir}) + +target_link_libraries(rtn_centered_text PUBLIC rnoh) +``` + + + +#### ArkTS Fabric 和 C-API Fabric 共有部分 + +修改 `oh-package.json5`,`hvigorfile.ts`,`module.json5`,`build-profile.json5`,或自行创建 + + + +#### **oh-package.json5** + +```json +{ + "license": "ISC", + "types": "", + "devDependencies": {}, + "name": "rtn-centered-text", + "description": "", + "main": "index.ets", // 仅 ArkTs 的 Fabric 需要 + "version": "0.0.1", + "dependencies": { + "@rnoh/react-native-openharmony": "file:../react_native_openharmony.har" + } +} +``` + + + + + +#### **hvigorfile.ts** + +```ts +export { harTasks } from "@ohos/hvigor-ohos-plugin"; +``` + + + + + +#### **module.json5** + +```json +{ + "module": { + "name": "rtn_centered_text", + "type": "har", + "deviceTypes": ["default"] + } +} +``` + + + + + +#### **build-profile.json5** + +```json +{ + "apiType": "stageMode", + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + } + ] +} +``` + + + +### 5. 将 Fabric 组件添加到 App + +#### 5.1 配置 RN 工程,进行打包安装 + +执行以下操作,假设 MyApp 为您的 App 工程路径 + +```bash +// 进入模块工程 +cd RTNCenteredText + +// 打包模块 +npm pack + +// 进入 App 工程 +cd ../MyApp + +// 本地路径安装模块 +npm i file:../RTNCenteredText/rtn-centered-text-0.0.1.tgz + +``` + +此命令会将 RTNCenteredText 模块添加到 App 内的 node_modules 目录。 + +#### 5.2 原生工程配置项 + +> [!tip] 待完善能力:HarmonyOS 平台目前暂时不支持 AutoLink,所以需要自行配置。 + +首先使用 DevEco Studio 打开 React-Native 项目里的鸿蒙工程 `harmony` + +目前 HarmonyOS 工程暂不支持引入工程外的模块,所以需要手动将模块的 HarmonyOS 源码复制到工程内。 + +- 复制 `RTNCenteredText/harmony/rtn_centered_text` 到 `harmony` 工程根目录下。 + +- 修改 `MyApp/harmony/build-profile.json5`,在 modules 字段添加: + +```json +{ +... + modules: [ + ... + { + name: 'rtn_centered_text', + srcPath: './rtn_centered_text', + } + ] +} +``` +使得 rtn_centered_text 能够被识别为模块,当文件夹右下角出现蓝色小方块时,说明识别成功。 + +- 在工程根目录的 `MyApp/harmony/oh-package.json5` 添加 overrides 字段 + +```json +{ + ... + "overrides": { + "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // RNOH SDK har包路径或源码路径 + } +} +``` + +- 打开 `MyApp/harmony/entry/oh-package.json5`,添加以下依赖,引入鸿蒙原生端的代码 + +```json +"dependencies": { + "@rnoh/react-native-openharmony": "file:../react_native_openharmony.har", // RNOH SDK har包路径或源码路径 + "rtn-centered-text": "file:../rtn_centered_text" + } +``` + +- 点击右上角的 `sync` 按钮同步工程,或在终端运行以下命令 + +```bash +cd entry +ohpm install +``` + +##### 5.2.1 ArkTS 组件特有配置项 + +打开 `MyApp/harmony/entry/src/main/ets/pages/Index.ets`,添加: + +```diff +... ++ import { RTNCenteredText } from "rtn-centered-text" + ++ const arkTsComponentNames: Array = [RTNCenteredText.NAME]; +@Builder +export function buildCustomRNComponent(ctx: ComponentBuilderContext) { + Stack() { ++ if (ctx.componentName === RTNCenteredText.NAME) { ++ RTNCenteredText({ ++ tag: ctx.tag, ++ ctx: ctx.rnComponentContext, ++ }) ++ } + //... + } + .position({x: 0, y: 0}) +} +//... +``` + +打开 `MyApp/harmony/entry/src/main/ets/RNPackageFactory.ts`,添加: + +```diff +import type {RNPackageContext, RNPackage} from '@rnoh/react-native-openharmony/ts'; ++ import { RTNCenteredTextPackage } from "rtn-centered-text/ts"; + +export function createRNPackages(ctx: RNPackageContext): RNPackage[] { + return [ ++ new RTNCenteredTextPackage(ctx), + ]; +} +``` + +编译、运行即可。 + +##### 5.2.2 C-API 组件特有配置项 + +打开 `MyApp/harmony/entry/src/main/cpp/CMakeLists.txt`,添加: + +```diff +project(rnapp) +cmake_minimum_required(VERSION 3.4.1) +set(CMAKE_SKIP_BUILD_RPATH TRUE) +set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules") ++ set(OH_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") ++ set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated") +set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp") +set(LOG_VERBOSITY_LEVEL 1) +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") +set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use +add_compile_definitions(WITH_HITRACE_SYSTRACE) + +add_subdirectory("${RNOH_CPP_DIR}" ./rn) + +# RNOH_BEGIN: manual_package_linking_1 +add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package) ++ add_subdirectory("${OH_MODULES_DIR}/rtn-centered-text/src/main/cpp" ./centered-text) +# RNOH_END: manual_package_linking_1 + +file(GLOB GENERATED_CPP_FILES "./generated/*.cpp") + +add_library(rnoh_app SHARED + ${GENERATED_CPP_FILES} + "./PackageProvider.cpp" + "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp" +) +target_link_libraries(rnoh_app PUBLIC rnoh) + +# RNOH_BEGIN: manual_package_linking_2 +target_link_libraries(rnoh_app PUBLIC rnoh_sample_package) ++ target_link_libraries(rnoh_app PUBLIC rtn_centered_text) +# RNOH_END: manual_package_linking_2 +``` + +打开 `MyApp/harmony/entry/src/main/cpp/PackageProvider.cpp`,添加: + +```diff +#include "RNOH/PackageProvider.h" +#include "SamplePackage.h" ++ #include "CenteredTextPackage.h" ++ #include "generated/RNOHGeneratedPackage.h" +using namespace rnoh; + +std::vector> PackageProvider::getPackages(Package::Context ctx) { + return { + std::make_shared(ctx), + std::make_shared(ctx), ++ std::make_shared(ctx), + }; +} +``` + +编译、运行即可。 + +#### 5.3 JavaScript + +最后,操作以下步骤,您就可以在 JavaScript 调用组件了。 + +1. 在 js 文件中导入组件。假设要在 App.js 进行导入,需要添加这行代码: + +```js +import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; +``` + +2. 接下来,在 React Native 组件里进行调用。调用的语法和其它组件相同: + +**App.js** + + + +#### **App.js** + +```js +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow strict-local + */ +import React from "react"; +import type { Node } from "react"; +import { SafeAreaView } from "react-native"; +import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; + +const App: () => Node = () => { + // ... other App code ... + return ( + + + + ); +}; + +export default App; +``` + + + +现在,您可以运行 App 并查看在屏幕上显示的组件。 + +> [!TIP] 可通过 npm run start 使用热更新 + +#### 5.4 如何将 HarmonyOS 原生部分的代码打成 *.har 包 +当完成上述操作,调试验证完毕您所需要的功能开发后,即可进行模块 har 打包操作 + +1. 执行 Build/Make Module 'rtn_centered_text' + +2. 查看目录 rtn_centered_text/build/default/outputs/default (模块 har 包生成的默认路径) + +3. 将上一步操作中所生成的 *.har 文件复制到最开始构建的三方库 RTNCenteredText 下的 harmony 文件夹下 + +```md +RTNCenteredText + └──harmony + ├──rtn_centered_text + └──rtn_centered_text.har +``` \ No newline at end of file diff --git a/zh-cn/fabric-arkts.md b/zh-cn/fabric-arkts.md new file mode 100644 index 0000000000000000000000000000000000000000..ca218c08941e025dddd233a728af6129af49dc15 --- /dev/null +++ b/zh-cn/fabric-arkts.md @@ -0,0 +1,598 @@ +> [!WARNING] 本文档仅用于三方库使用和开发指导,不涉及任何 React Native OpenHarmony 框架的信息,且会随着 React Native OpenHarmony 框架持续迭代更新,当前版本不代表最终展示版本。 + +# 使用 ArkTS API 开发 Fabric 组件 + +此文档主要介绍在原生侧使用 ArkTS API 开发 Fabric 组件,React 侧请阅读入口文档 [Fabric 组件](/zh-cn/fabric.md) + +### 1. Codegen 配置 + +#### 1.1 配置 `package.json` 文件 + +请在 `RTNCenteredText` 的根目录创建 `package.json` 文件。 + +```json +{ + "name": "rtn-centered-text", + "version": "0.0.1", + "description": "Showcase a Fabric component with a centered text", + "react-native": "src/index", + "source": "src/index", + "files": [ + "src", + "harmony", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "harmony"], + "repository": "https://github.com//rtn-centered-text", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rtn-centered-text/issues" + }, + "homepage": "https://github.com//rtn-centered-text#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + } +} +``` + +> [!TIP] 可以通过添加 harmony/alias 字段为第三方库指定别名,通常在移植某个第三方库时使用,以确保开发者在使用时仍能保持原库名不变。 + +#### 1.2 使用 codegen-lib-harmony 工具 + +此步操作是要使用 Codegen 来为组件生成脚手架代码。在 `react-native-harmony-cli >= 0.0.27` 的版本中提供了 codegen-lib-harmony 工具,开发者可以使用该脚本随时执行 Codegen,可以选择运行时生成,也可以选择将这部分生成的脚手架代码内置到模块内。以下将介绍内置到模块内的用法。 + +在 `RTNCenteredText/package.json` 中新增 `react-native-harmony-cli` 开发依赖: + +```json +"devDependencies": { + "react-native-harmony-cli": "npm:@react-native-oh/react-native-harmony-cli@^0.0.27", + "@react-native-community/cli": "11.3.6" +}, +``` + +在 `scripts` 字段配置 codegen-lib 脚本: + +```json +"scripts": { + "codegen-lib": "react-native codegen-lib-harmony --no-safety-check --npm-package-name rtn-centered-text --cpp-output-path ./harmony/rtn_centered_text/src/main/cpp/generated --ets-output-path ./harmony/rtn_centered_text/src/main/ets/generated --arkts-components-spec-paths ./src" + } +``` + +该脚本根据指定的 spec 文件,将脚手架代码生成到指定目录。 + +> codegen-lib-harmony 参数介绍: + +1. --npm-package-name: npm 包名(package.json 的 name 字段) +2. --cpp-output-path: 指定用于存储生成的 C++ 文件的输出目录的相对路径 +3. --ets-output-path: 指定用于存储生成的 ArkTS 文件的输出目录的相对路径 +4. --arkts-components-spec-paths: Fabric 声明文件路径或文件夹路径(ArkTS API 开发) + +执行 codegen-lib 脚本后,可以在 `RTNCenteredText/harmony/rtn_centered_text/src/main` 文件夹中找到 Codegen 代码 + +```md +. +├── cpp +│ └── generated +│ ├── RNOH +│ │ └── generated +│ │ ├── BaseRtnCenteredTextPackage.h +│ │ └── components +│ │ └── RTNCenteredTextJSIBinder.h +│ └── react +│ └── renderer +│ └── components +│ └── rtn_centered_text +│ ├── ComponentDescriptors.h +│ ├── EventEmitters.cpp +│ ├── EventEmitters.h +│ ├── Props.cpp +│ ├── Props.h +│ ├── ShadowNodes.cpp +│ ├── ShadowNodes.h +│ ├── States.cpp +│ └── States.h +├── ets +│ └── generated +│ ├── components +│ │ ├── RTNCenteredText.ts +│ │ └── ts.ts +│ ├── index.ets +│ ├── ts.ts +│ └── turboModules +│ └── ts.ts +``` + +生成的代码分为两个文件夹: + +- `cpp` 包含 C++ 代码,以让 JS 和 C++/ArkTS 正确交互 +- `ets` 包含平台特定的代码 + +在 `cpp/generated` 文件夹中,是所有连接 JS 和 HarmonyOS 的样板代码。 + +- `cpp/generated/RNOH/generated` 下生成的是自定义组件对接 RNOH 框架所需的文件; +- `cpp/generated/react/renderer/components/rtn_centered_text` 下包含自定义组件所需的粘合代码。 + +在 `ets/generated` 文件夹中,是该自定义组件的 ArkTS 类型声明,包括了改组件所拥有的属性、事件、命令等接口的定义。 + +### 2. 使用 ArkTS API 实现原生组件 + +HarmonyOS 平台中 ArkTS 版本的 Fabric 组件的原生代码必须包含以下三个部分: + +1. 创建用于实现组件的 `RTNCenteredText.ets` +2. 创建 `RTNCenteredTextPackage.ts` 和 `RTNCenteredTextPackage.h` +3. 创建用于导出模块的 `Index.ets` 和 `ts.ts` + +HarmonyOS 第三方库目录文件结构应为如下: + +```md +harmony +└── rtn_centered_text + ├── Index.ets + ├── build-profile.json5 + ├── hvigorfile.ts + ├── oh-package.json5 + ├── src + │ └── main + │ ├── cpp + │ │ ├── CMakeLists.txt + │ │ ├── RTNCenteredTextPackage.h + │ │ └── generated + │ ├── ets + │ │ ├── RTNCenteredText.ets + │ │ ├── RTNCenteredTextPackage.ts + │ │ └── generated + │ └── module.json5 + └── ts.ts +``` + +创建 `ets/RTNCenteredText.ets`,`ets/RTNCenteredPackage.ts` + + + +#### **RTNCenteredText.ets** + +```ts +import { RNComponentContext, RNViewBase } from '@rnoh/react-native-openharmony'; +import { RNC } from "./generated" + +@Component +export struct RTNCenteredText { + public static readonly NAME = RNC.RTNCenteredText.NAME + public ctx!: RNComponentContext + public tag: number = 0 + @State private descriptorWrapper: RNC.RTNCenteredText.DescriptorWrapper = {} as RNC.RTNCenteredText.DescriptorWrapper + private eventEmitter: RNC.RTNCenteredText.EventEmitter | undefined = undefined + private cleanUpCallbacks: (() => void)[] = [] + + aboutToAppear() { + this.eventEmitter = new RNC.RTNCenteredText.EventEmitter(this.ctx.rnInstance, this.tag) + this.onDescriptorWrapperChange(this.ctx.descriptorRegistry.findDescriptorWrapperByTag(this.tag)!) + this.cleanUpCallbacks.push(this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, + (_descriptor, newDescriptorWrapper) => { + this.onDescriptorWrapperChange(newDescriptorWrapper! as RNC.RTNCenteredText.DescriptorWrapper) + } + )) + } + + private onDescriptorWrapperChange(descriptorWrapper: RNC.RTNCenteredText.DescriptorWrapper) { + this.descriptorWrapper = descriptorWrapper + } + + aboutToDisappear() { + this.cleanUpCallbacks.forEach(cb => cb()) + } + + build() { + RNViewBase({ ctx: this.ctx, tag: this.tag }) { + Text(this.descriptorWrapper.props.text) + .height("100%") + .width("100%") + .fontSize(30) + .textAlign(TextAlign.Center) + .fontColor(this.descriptorWrapper.props.color?.toRGBAString()) + .onTouch((e) => { + this.eventEmitter?.emit("textTouch", { type: e.type }) + }) + } + } +} +``` + + + +这里定义了原生视图 RTNCenteredText,主要功能是显示一个居中的文本,并响应触摸事件。组件通过 @State 和 descriptorWrapper 管理其状态,并通过 eventEmitter 发射事件。在生命周期方法 aboutToAppear 和 aboutToDisappear 中处理组件的初始化和清理操作。UI 渲染中通过 Text 控件展示内容,并支持根据描述符动态更新属性。 + + + +#### **RTNCenteredPackage.ts** + +```ts +import { RNPackage } from "@rnoh/react-native-openharmony/ts"; +import type { + DescriptorWrapperFactoryByDescriptorTypeCtx, + DescriptorWrapperFactoryByDescriptorType, +} from "@rnoh/react-native-openharmony/ts"; +// import codegen 生成的内容 +import { RNC } from "./generated/ts"; + +export class RTNCenteredTextPackage extends RNPackage { + createDescriptorWrapperFactoryByDescriptorType( + ctx: DescriptorWrapperFactoryByDescriptorTypeCtx + ): DescriptorWrapperFactoryByDescriptorType { + return { + [RNC.RTNCenteredText.NAME]: (ctx) => + new RNC.RTNCenteredText.DescriptorWrapper(ctx.descriptor), + }; + } +} +``` + + + +创建 `ts.ts` + + + +#### **ts.ts** + +```ts +export * from "./src/main/ets/RTNCenteredTextPackage"; +``` + + + +创建 `Index.ets` + + + +#### **Index.ets** + +```ts +export * from "./ts"; +export * from "./src/main/ets/RTNCenteredText"; +``` + + + +创建 `cpp/RTNCenteredTextPackage.h` + + + +#### **RTNCenteredTextPackage.h** + +```cpp +#pragma once + +#include "RNOH/generated/BaseRtnCenteredTextPackage.h" + +namespace rnoh { + +class RTNCenteredTextPackage : public BaseRtnCenteredTextPackage { + using Super = BaseRtnCenteredTextPackage; + +public: + RTNCenteredTextPackage(Package::Context ctx) : Super(ctx) {} +}; +} // namespace rnoh +``` + + + + +创建 `cpp/CMakeLists.txt` + + + +#### **CMakeLists.txt** + +```txt +# 设置 Codegen 生成目录,指定 generated 目录路径 +set(rtn_centered_text_generated_dir "${CMAKE_CURRENT_SOURCE_DIR}/generated") + +# 使用 GLOB_RECURSE 递归地查找所有在 generated 目录下的 .cpp 文件,并将其存储到变量 rtn_centered_text_generated_SRC 中 +file(GLOB_RECURSE rtn_centered_text_generated_SRC "${rtn_centered_text_generated_dir}/**/*.cpp") + +# 查找当前目录下的所有 .cpp 文件,并将其存储到变量 rtn_centered_text_SRC 中 +# CONFIGURE_DEPENDS 表示如果这些文件被修改,CMake 会重新配置 +file(GLOB rtn_centered_text_SRC CONFIGURE_DEPENDS *.cpp) + +# 创建一个共享库 rtn_centered_text,包含两部分:rtn_centered_text_SRC 和 rtn_centered_text_generated_SRC +add_library(rtn_centered_text SHARED ${rtn_centered_text_SRC} ${rtn_centered_text_generated_SRC}) + +# 为目标库 rtn_centered_text 设置包含路径,这些路径会包含当前源目录和 Codegen 生成文件所在的目录 +target_include_directories(rtn_centered_text PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${rtn_centered_text_generated_dir}) + +# 将库 rtn_centered_text 链接到 rnoh sdk,意味着 rtn_centered_text 使用 rnoh sdk 中的功能 +target_link_libraries(rtn_centered_text PUBLIC rnoh) +``` + + + +创建 `oh-package.json5`,`hvigorfile.ts`,`module.json5`,`build-profile.json5` + + + +#### **oh-package.json5** + +```json +{ + license: 'MIT', + types: '', + name: 'rtn-centered-text', + description: '', + main: 'Index.ets', + type: 'module', + version: '1.0.0', + dependencies: { + "@rnoh/react-native-openharmony": '0.72.38' // 执行指定依赖的 RN SDK 版本 + }, +} +``` + + + + + +#### **hvigorfile.ts** + +```ts +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; +``` + + + + + +#### **src/main/module.json5** + +```json +{ + module: { + name: 'rtn_centered_text', + type: 'har', + deviceTypes: ['default'], + }, +} +``` + + + + + +#### **build-profile.json5** + +```json +{ + "apiType": "stageMode", + "targets": [ + { + "name": "default", + } + ] +} +``` + + + +### 3. 将 Fabric 组件添加到 App + +#### 3.1 配置 RN 工程,进行打包安装 + +执行以下操作,假设 exampleApp 为您的 App 工程 + +```bash +// 进入三方库工程 +cd RTNCenteredText + +// 打包三方库,rtn-centered-text-0.0.1.tgz +npm pack + +// 进入 App 工程 +cd ../exampleApp + +// 本地路径安装三方库,若需要更新版本,请先执行 npm uninstall rtn-centered-text +npm i file:../RTNCenteredText/rtn-centered-text-0.0.1.tgz + +``` + +此命令会将 RTNCenteredText 模块安装到 exampleApp 工程, 您可以在 `node_modules/rtn-centered-text` 目录找到相关文件。 + +#### 3.2 原生工程配置项 + +> [!tip] 待完善能力:HarmonyOS 平台目前暂时不支持 AutoLink,所以需要自行配置。 + +首先使用 DevEco Studio 打开 exampleApp 项目里的鸿蒙工程 `harmony` + +- 修改 `exampleApp/harmony/build-profile.json5`,在 modules 字段添加: + +```json +{ +... + modules: [ + ... + { + name: 'rtn_centered_text', + srcPath: '../../RTNCenteredText/harmony/rtn_centered_text', + }, + ] +} +``` +指向 rtn_centered_text 模块所在路径,使得 rtn_centered_text 能够被识别为工程 module。 + +- 在工程根目录的 `exampleApp/harmony/oh-package.json5` 添加 RN SDK 依赖和 overrides 字段: + +> [!TIP] overrides 字段是为了让工程依赖同一个版本的 RN SDK,最终的依赖版本以 overrides 字段指定的为准。 + +```json +{ + ... + "dependencies": { + "@rnoh/react-native-openharmony": "0.72.38" + }, + "overrides": { + "@rnoh/react-native-openharmony": "0.72.38" // 请按需选择版本 + } +} +``` + +- 打开 `exampleApp/harmony/entry/oh-package.json5`,添加以下依赖,引入 RTNCenteredText 的原生代码: + +```json +"dependencies": { + "rtn-centered-text": "../../../RTNCenteredText/harmony/rtn_centered_text" // rtn_centered_text 模块所在路径 + } +``` + +- 点击右上角的 `sync` 按钮同步工程,或在终端运行以下命令 + +```bash +cd entry +ohpm install +``` + +##### 3.2.1 链接 CPP + +打开 `exampleApp/harmony/entry/src/main/cpp/CMakeLists.txt`,添加: + +```diff +# ... ++ set(OH_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") + +# RNOH_BEGIN: manual_package_linking_1 ++ add_subdirectory("${OH_MODULES_DIR}/rtn-centered-text/src/main/cpp" ./centered-text) +# RNOH_END: manual_package_linking_1 + +# RNOH_BEGIN: manual_package_linking_2 ++ target_link_libraries(rnoh_app PUBLIC rtn_centered_text) +# RNOH_END: manual_package_linking_2 +``` + +打开 `exampleApp/harmony/entry/src/main/cpp/PackageProvider.cpp`,添加: + +```diff ++ #include "RTNCenteredTextPackage.h" + +using namespace rnoh; + +std::vector> PackageProvider::getPackages(Package::Context ctx) { + return { ++ std::make_shared(ctx), + }; +} +``` + +##### 3.2.2 ArkTS Fabric 组件特有配置项 + +打开 `exampleApp/harmony/entry/src/main/ets/pages/Index.ets`,添加: + +```diff ++ import { RTNCenteredText } from "rtn-centered-text" + ++ const arkTsComponentNames: Array = [RTNCenteredText.NAME]; + +@Builder +export function buildCustomRNComponent(ctx: ComponentBuilderContext) { ++ if (ctx.componentName === RTNCenteredText.NAME) { ++ RTNCenteredText({ ++ tag: ctx.tag, ++ ctx: ctx.rnComponentContext, ++ }) ++ } +} +``` + +打开 `exampleApp/harmony/entry/src/main/ets/RNPackageFactory.ts`,添加: + +```diff +import type {RNPackageContext, RNPackage} from '@rnoh/react-native-openharmony/ts'; ++ import { RTNCenteredTextPackage } from "rtn-centered-text/ts"; + +export function createRNPackages(ctx: RNPackageContext): RNPackage[] { + return [ ++ new RTNCenteredTextPackage(ctx), + ]; +} +``` + +编译、运行即可。 + +#### 3.3 JavaScript + +最后,操作以下步骤,您就可以在 JavaScript 调用组件了。 + +1. 在 js 文件中导入组件。假设要在 App.js 进行导入,需要添加这行代码: + +```js +import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; +``` + +2. 接下来,在 React Native 组件里进行调用。调用的语法和其它组件相同: + + + +#### **App.js** + +```js +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow strict-local + */ +import React from "react"; +import type { Node } from "react"; +import { SafeAreaView } from "react-native"; +import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; + +const App: () => Node = () => { + // ... other App code ... + const [content, setContent] = useState('Hello World!'); + return ( + + { + if (e.type == '0') { + setContent('Touch Down'); + return; + } + if (e.type == '1') { + setContent('Touch Up'); + return; + } + if (e.type == '2') { + setContent('Touch Move'); + return; + } + }} + /> + + ); +}; + +export default App; +``` + + + +现在,您可以运行 App 并查看在屏幕上显示的组件。 + +> [!TIP] 可通过 `hdc rport tcp:8081 tcp:8081 && npm run start` 使用热更新 + +### 4. 其他 + +#### 4.1 如何将 HarmonyOS 原生部分的代码打成 *.har 包 + +当完成上述操作,调试验证完毕您所需要的功能开发后,即可进行模块 har 打包操作 + +1. 打开 DevEco Studio,选中 `rtn_centered_text` 模块,点击 Build -> Make Module 'rtn_centered_text'; + +2. .har 会生成到目录 rtn_centered_text/build/default/outputs/default (模块 har 包生成的默认路径) \ No newline at end of file diff --git a/zh-cn/fabric-capi.md b/zh-cn/fabric-capi.md new file mode 100644 index 0000000000000000000000000000000000000000..88aa87950ec8a5a7a0819341317acaa42f710f2f --- /dev/null +++ b/zh-cn/fabric-capi.md @@ -0,0 +1,572 @@ +> [!WARNING] 本文档仅用于三方库使用和开发指导,不涉及任何 React Native OpenHarmony 框架的信息,且会随着 React Native OpenHarmony 框架持续迭代更新,当前版本不代表最终展示版本。 + +# 使用 C-API 开发 Fabric 组件 + +此文档主要介绍在原生侧使用 C-API 开发 Fabric 组件,React 侧请阅读入口文档 [Fabric 组件](/zh-cn/fabric.md) + +### 1. Codegen 配置 + +#### 1.1 配置 `package.json` 文件 + +请在 `RTNCenteredText` 的根目录创建 `package.json` 文件。 + +```json +{ + "name": "rtn-centered-text", + "version": "0.0.1", + "description": "Showcase a Fabric component with a centered text", + "react-native": "src/index", + "source": "src/index", + "files": [ + "src", + "harmony", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "harmony"], + "repository": "https://github.com//rtn-centered-text", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rtn-centered-text/issues" + }, + "homepage": "https://github.com//rtn-centered-text#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + } +} +``` + +> [!TIP] 可以通过添加 harmony/alias 字段为第三方库指定别名,通常在移植某个第三方库时使用,以确保开发者在使用时仍能保持原库名不变。 + +#### 1.2 使用 codegen-lib-harmony 工具 + +此步操作是要使用 Codegen 来为组件生成脚手架代码。在 `react-native-harmony-cli >= 0.0.27` 的版本中提供了 codegen-lib-harmony 工具,开发者可以使用该脚本随时执行 Codegen,可以选择运行时生成,也可以选择将这部分生成的脚手架代码内置到模块内。以下将介绍内置到模块内的用法。 + +在 `RTNCenteredText/package.json` 中新增 `react-native-harmony-cli` 开发依赖: + +```json +"devDependencies": { + "react-native-harmony-cli": "npm:@react-native-oh/react-native-harmony-cli@^0.0.27", + "@react-native-community/cli": "11.3.6" +}, +``` + +在 `scripts` 字段配置 codegen-lib 脚本: + +```json +"scripts": { + "codegen-lib": "react-native codegen-lib-harmony --no-safety-check --npm-package-name rtn-centered-text --cpp-output-path ./harmony/rtn_centered_text/src/main/cpp/generated --ets-output-path ./harmony/rtn_centered_text/src/main/ets/generated --cpp-components-spec-paths ./src" + } +``` + +该脚本根据指定的 spec 文件,将脚手架代码生成到指定目录。 + +> codegen-lib-harmony 参数介绍: + +1. --npm-package-name: npm 包名(package.json 的 name 字段) +2. --cpp-output-path: 指定用于存储生成的 C++ 文件的输出目录的相对路径 +3. --ets-output-path: 指定用于存储生成的 ArkTS 文件的输出目录的相对路径 +4. --cpp-components-spec-paths: Fabric 声明文件路径或文件夹路径(C-API 开发) + +执行 codegen-lib 脚本后,可以在 `RTNCenteredText/harmony/rtn_centered_text/src/main` 文件夹中找到 Codegen 代码 + +```md +. +├── cpp +│ └── generated +│ ├── RNOH +│ │ └── generated +│ │ ├── BaseRtnCenteredTextPackage.h +│ │ └── components +│ │ ├── BaseRTNCenteredTextComponentInstance.h +│ │ └── RTNCenteredTextJSIBinder.h +│ └── react +│ └── renderer +│ └── components +│ └── rtn_centered_text +│ ├── ComponentDescriptors.h +│ ├── EventEmitters.cpp +│ ├── EventEmitters.h +│ ├── Props.cpp +│ ├── Props.h +│ ├── ShadowNodes.cpp +│ ├── ShadowNodes.h +│ ├── States.cpp +│ └── States.h +└── ets + └── generated + ├── components + │ └── ts.ts + ├── index.ets + ├── ts.ts + └── turboModules + └── ts.ts +``` + +生成的代码分为两个文件夹: + +- `cpp` 包含 C++ 代码,以让 JS 和 C++/ArkTS 正确交互 +- `ets` 包含平台特定的代码 + +在 `cpp/generated` 文件夹中,是所有连接 JS 和 HarmonyOS 的样板代码。 + +- `cpp/generated/RNOH/generated` 下生成的是自定义组件对接 RNOH 框架所需的文件; +- `cpp/generated/react/renderer/components/rtn_centered_text` 下包含自定义组件所需的粘合代码。 + +在 `ets/generated` 文件夹中,因为使用 C-API 开发组件,所以该文件夹中的文件内容为空。 + +### 2. 使用 C-API 实现原生组件 + +HarmonyOS 平台中 C-API 版本的 Fabric 组件的原生代码必须包含以下三个部分: + +1. 创建用于实现组件的 `RTNCenteredTextComponentInstance` +2. 创建 `RTNCenteredTextCAPIPackage.h` +3. 创建 `Index.ets` (空实现) + +HarmonyOS 第三方库目录文件结构应为如下: + +```md +harmony +└── rtn_centered_text + ├── Index.ets + ├── build-profile.json5 + ├── hvigorfile.ts + ├── oh-package.json5 + ├── src + │ └── main + │ ├── cpp + │ │ ├── CMakeLists.txt + │ │ ├── RTNCenteredTextCAPIPackage.h + │ │ ├── RTNCenteredTextComponentInstance.cpp + │ │ ├── RTNCenteredTextComponentInstance.h + │ │ └── generated + │ ├── ets + │ │ └── generated + │ └── module.json5 + └── ts.ts +``` + +创建 `cpp/RTNCenteredTextComponentInstance.h`,`cpp/RTNCenteredTextComponentInstance.cpp`,`cpp/RTNCenteredTextCAPIPackage.h` + + + +#### **RTNCenteredTextComponentInstance.h** + +```cpp +#pragma once + +#include "RNOH/arkui/StackNode.h" +#include "RNOH/arkui/TextNode.h" +#include "RNOH/generated/components/BaseRTNCenteredTextComponentInstance.h" + +namespace rnoh { +class RTNCenteredTextComponentInstance : public BaseRTNCenteredTextComponentInstance, public TouchEventHandler { + using super = BaseRTNCenteredTextComponentInstance; + + enum class ActionType { Cancel, Down, Move, Up }; + + StackNode m_stackNode; + TextNode m_textNode; + +public: + RTNCenteredTextComponentInstance(Context context); + ~RTNCenteredTextComponentInstance() override; + + ArkUINode &getLocalRootArkUINode() { return m_stackNode; }; + + void onTouchEvent(ArkUI_UIInputEvent *e) override; + +protected: + void onPropsChanged(SharedConcreteProps const &props) override; +}; + +} // namespace rnoh + +``` + + + + + +#### **RTNCenteredTextComponentInstance.cpp** + +```cpp +#include "RTNCenteredTextComponentInstance.h" +#include +#include +#include + +namespace rnoh { + +RTNCenteredTextComponentInstance::RTNCenteredTextComponentInstance(Context context) : super(std::move(context)) { + m_stackNode.insertChild(m_textNode, 0); + ArkUINodeRegistry::getInstance().registerTouchHandler(&m_textNode, this); + NativeNodeApi::getInstance()->registerNodeEvent(m_textNode.getArkUINodeHandle(), NODE_TOUCH_EVENT, NODE_TOUCH_EVENT, + 0); +}; + +RTNCenteredTextComponentInstance::~RTNCenteredTextComponentInstance() { + NativeNodeApi::getInstance()->unregisterNodeEvent(m_textNode.getArkUINodeHandle(), NODE_TOUCH_EVENT); + ArkUINodeRegistry::getInstance().unregisterTouchHandler(&m_textNode); +} + +void RTNCenteredTextComponentInstance::onPropsChanged(SharedConcreteProps const &props) { + super::onPropsChanged(props); + m_textNode.setTextContent(props->text); + m_textNode.setFontSize(30.0); + m_textNode.setTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER); + if (props->rawProps.count("color") != 0 && !props->rawProps["color"].isNull()) { + uint32_t color = std::stoul(props->rawProps["color"].asString(), nullptr, 16); + m_textNode.setFontColor(color); + } +}; + +void RTNCenteredTextComponentInstance::onTouchEvent(ArkUI_UIInputEvent *e) { + auto action = OH_ArkUI_UIInputEvent_GetAction(e); + auto actionType = static_cast(action); + if (actionType == ActionType::Down) { + m_eventEmitter->onTextTouch({.type = 0}); + return; + } + if (actionType == ActionType::Up) { + m_eventEmitter->onTextTouch({.type = 1}); + return; + } + if (actionType == ActionType::Move) { + m_eventEmitter->onTextTouch({.type = 2}); + return; + } +} + +} // namespace rnoh +``` + + + +这里定义了一个原生视图 RTNCenteredText,主要功能是显示居中的文本并响应触摸事件。该组件继承自 Codegen 生成的 `BaseRTNCenteredTextComponentInstance`,并重写了 `onPropsChanged` 方法以处理属性更新,同时重写了 `onTouchEvent` 方法以处理点击事件。在 UI 渲染过程中,通过 m_textNode 创建的 Text 控件展示文本内容。 + + + +#### **RTNCenteredTextCAPIPackage.h** + +```cpp +#pragma once + +#include "RNOH/generated/BaseRtnCenteredTextPackage.h" +#include "RTNCenteredTextComponentInstance.h" + +namespace rnoh { + +class RTNCenteredTextCAPIPackage : public BaseRtnCenteredTextPackage { + using Super = BaseRtnCenteredTextPackage; + +public: + RTNCenteredTextCAPIPackage(Package::Context ctx) : Super(ctx) {} + + ComponentInstance::Shared createComponentInstance(const ComponentInstance::Context &ctx) { + if (ctx.componentName == "RTNCenteredText") { + // C-API Fabric 需要创建 ComponentInstance + return std::make_shared(ctx); + } + return nullptr; + }; +}; + +} // namespace rnoh +``` + + + +创建 `cpp/CMakeLists.txt` + + + +#### **CMakeLists.txt** + +```txt +# 设置 Codegen 生成目录,指定 generated 目录路径 +set(rtn_centered_text_generated_dir "${CMAKE_CURRENT_SOURCE_DIR}/generated") + +# 使用 GLOB_RECURSE 递归地查找所有在 generated 目录下的 .cpp 文件,并将其存储到变量 rtn_centered_text_generated_SRC 中 +file(GLOB_RECURSE rtn_centered_text_generated_SRC "${rtn_centered_text_generated_dir}/**/*.cpp") + +# 查找当前目录下的所有 .cpp 文件,并将其存储到变量 rtn_centered_text_SRC 中 +# CONFIGURE_DEPENDS 表示如果这些文件被修改,CMake 会重新配置 +file(GLOB rtn_centered_text_SRC CONFIGURE_DEPENDS *.cpp) + +# 创建一个共享库 rtn_centered_text,包含两部分:rtn_centered_text_SRC 和 rtn_centered_text_generated_SRC +add_library(rtn_centered_text SHARED ${rtn_centered_text_SRC} ${rtn_centered_text_generated_SRC}) + +# 为目标库 rtn_centered_text 设置包含路径,这些路径会包含当前源目录和 Codegen 生成文件所在的目录 +target_include_directories(rtn_centered_text PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${rtn_centered_text_generated_dir}) + +# 将库 rtn_centered_text 链接到 rnoh sdk,意味着 rtn_centered_text 使用 rnoh sdk 中的功能 +target_link_libraries(rtn_centered_text PUBLIC rnoh) +``` + + + +创建 `Index.ets`,空实现即可。 + + + +#### **Index.ets** + +```ts +// NO-OP +``` + + + +创建 `oh-package.json5`,`hvigorfile.ts`,`module.json5`,`build-profile.json5` + + + +#### **oh-package.json5** + +```json +{ + license: 'MIT', + types: '', + name: 'rtn-centered-text', + description: '', + main: 'Index.ets', + type: 'module', + version: '1.0.0', + dependencies: { + "@rnoh/react-native-openharmony": '0.72.38' // 执行指定依赖的 RN SDK 版本 + }, +} +``` + + + + + +#### **hvigorfile.ts** + +```ts +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { harTasks } from '@ohos/hvigor-ohos-plugin'; +``` + + + + + +#### **src/main/module.json5** + +```json +{ + module: { + name: 'rtn_centered_text', + type: 'har', + deviceTypes: ['default'], + }, +} +``` + + + + + +#### **build-profile.json5** + +```json +{ + "apiType": "stageMode", + "targets": [ + { + "name": "default", + } + ] +} +``` + + + +### 3. 将 Fabric 组件添加到 App + +#### 3.1 配置 RN 工程,进行打包安装 + +执行以下操作,假设 exampleApp 为您的 App 工程 + +```bash +// 进入三方库工程 +cd RTNCenteredText + +// 打包三方库,生成 rtn-centered-text-0.0.1.tgz +npm pack + +// 进入 App 工程 +cd ../exampleApp + +// 本地路径安装三方库,若需要更新版本,请先执行 npm uninstall rtn-centered-text +npm i file:../RTNCenteredText/rtn-centered-text-0.0.1.tgz + +``` + +此命令会将 RTNCenteredText 模块安装到 exampleApp 工程, 您可以在 `node_modules/rtn-centered-text` 目录找到相关文件。 + +#### 3.2 原生工程配置项 + +> [!tip] 待完善能力:HarmonyOS 平台目前暂时不支持 AutoLink,所以需要自行配置。 + +首先使用 DevEco Studio 打开 exampleApp 项目里的鸿蒙工程 `harmony` + +- 修改 `exampleApp/harmony/build-profile.json5`,在 modules 字段添加: + +```json +{ +... + modules: [ + ... + { + name: 'rtn_centered_text', + srcPath: '../../RTNCenteredText/harmony/rtn_centered_text', + }, + ] +} +``` +指向 rtn_centered_text 模块所在路径,使得 rtn_centered_text 能够被识别为工程 module。 + +- 在工程根目录的 `exampleApp/harmony/oh-package.json5` 添加 RN SDK 依赖和 overrides 字段: + +> [!TIP] overrides 字段是为了让工程依赖同一个版本的 RN SDK,最终的依赖版本以 overrides 字段指定的为准。 + +```json +{ + ... + "dependencies": { + "@rnoh/react-native-openharmony": "0.72.38" + }, + "overrides": { + "@rnoh/react-native-openharmony": "0.72.38" // 请按需选择版本 + } +} +``` + +- 打开 `exampleApp/harmony/entry/oh-package.json5`,添加以下依赖,引入 RTNCenteredText 的原生代码: + +```json +"dependencies": { + "rtn-centered-text": "../../../RTNCenteredText/harmony/rtn_centered_text" // rtn_centered_text 模块所在路径 + } +``` + +- 点击右上角的 `sync` 按钮同步工程,或在终端运行以下命令 + +```bash +cd entry +ohpm install +``` + +打开 `exampleApp/harmony/entry/src/main/cpp/CMakeLists.txt`,添加: + +```diff +# ... ++ set(OH_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") + +# RNOH_BEGIN: manual_package_linking_1 ++ add_subdirectory("${OH_MODULES_DIR}/rtn-centered-text/src/main/cpp" ./centered-text) +# RNOH_END: manual_package_linking_1 + +# RNOH_BEGIN: manual_package_linking_2 ++ target_link_libraries(rnoh_app PUBLIC rtn_centered_text) +# RNOH_END: manual_package_linking_2 +``` + +打开 `exampleApp/harmony/entry/src/main/cpp/PackageProvider.cpp`,添加: + +```diff ++ #include "RTNCenteredTextCAPIPackage.h" + +using namespace rnoh; + +std::vector> PackageProvider::getPackages(Package::Context ctx) { + return { ++ std::make_shared(ctx), + }; +} +``` + +编译、运行即可。 + +#### 3.3 JavaScript + +最后,操作以下步骤,您就可以在 JavaScript 调用组件了。 + +1. 在 js 文件中导入组件。假设要在 App.js 进行导入,需要添加这行代码: + +```js +import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; +``` + +2. 接下来,在 React Native 组件里进行调用。调用的语法和其它组件相同: + + + +#### **App.js** + +```js +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow strict-local + */ +import React from "react"; +import type { Node } from "react"; +import { SafeAreaView } from "react-native"; +import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; + +const App: () => Node = () => { + // ... other App code ... + const [content, setContent] = useState('Hello World!'); + return ( + + { + if (e.type == '0') { + setContent('Touch Down'); + return; + } + if (e.type == '1') { + setContent('Touch Up'); + return; + } + if (e.type == '2') { + setContent('Touch Move'); + return; + } + }} + /> + + ); +}; + +export default App; +``` + + + +现在,您可以运行 App 并查看在屏幕上显示的组件。 + +> [!TIP] 可通过 `hdc rport tcp:8081 tcp:8081 && npm run start` 使用热更新 + +### 4. 其他 + +#### 4.1 如何将 HarmonyOS 原生部分的代码打成 *.har 包 + +当完成上述操作,调试验证完毕您所需要的功能开发后,即可进行模块 har 打包操作 + +1. 打开 DevEco Studio,选中 `rtn_centered_text` 模块,点击 Build -> Make Module 'rtn_centered_text'; + +2. .har 会生成到目录 rtn_centered_text/build/default/outputs/default (模块 har 包生成的默认路径) \ No newline at end of file diff --git a/zh-cn/fabric.md b/zh-cn/fabric.md index b4c0629c455eeecfa781334b1e6fca3bc82bab2b..0fa07fa957642cc8c2f793119cfebcdd0d0c8537 100644 --- a/zh-cn/fabric.md +++ b/zh-cn/fabric.md @@ -1,5 +1,3 @@ -> [!WARNING] 本文档为非公开文档,仅用于三方库使用和开发指导,不涉及任何 React Native OpenHarmony 框架的信息,且会随着 React Native OpenHarmony 框架持续迭代更新,当前版本不代表最终展示版本。 - # Fabric 组件 Fabric 组件是一种使用 Fabric 渲染器渲染并展示在屏幕上的 UI 组件。 @@ -22,857 +20,119 @@ Fabric 组件是一种使用 Fabric 渲染器渲染并展示在屏幕上的 UI ### 1. 目录配置 -同样的,我们按照一般的三方库目录结构来配置: +首先,需要有一个已配置好支持 HarmonyOS 的 RN 工程,以下简称 `exampleApp`。 + +在 `exampleApp` 同级目录下新建三方库目录 `RTNCenteredText`。 + +并在 `RTNCenteredText` 下新建 `harmony` 和 `src` 文件夹。 ``` . -├── MyApp +├── exampleApp └── RTNCenteredText - ├── android(Android 的原生实现代码,若有) - ├── ios(iOS 的原生实现代码,若有) ├── harmony(HarmonyOS 的原生实现代码) └── src (js/ts代码) ``` ### 2. 声明 JavaScript 接口 -新架构要求必须使用强类型风格语言声明 JavaScript 接口(Flow 和 TypeScript 皆可)。Codegen 会根据这些接口声明来生成强类型的语言,其中包括 C++、Objective-C 和 Java。 +[React-Native 新架构](https://reactnative.cn/docs/the-new-architecture/landing-page) 要求必须使用强类型风格语言声明 JavaScript 接口(Flow 和 TypeScript 皆可)。Codegen 会根据这些接口声明来生成强类型的语言,其中包括 C++ 和 ArkTS。 对于声明类型的代码文件必须满足以下两点要求: 1. 文件必须使用 `NativeComponent` 命名,在使用 Flow 时,以 `.js` 或 `.jsx` 为后缀名;在使用 Typescript 时,以 `.ts` 或 `.tsx` 为后缀名。Codegen 只会找到匹配这些命名规则的文件; -2. 代码中必须要输出 HostComponent 对象。 +2. 代码中必须要输出 `HostComponent` 对象。 -以下是使用 Flow 和 TypeScript 声明的 RTNCenteredText 组件。在 `src` 目录中,创建一个命名为 `RTNCenteredText` 并带有相应后缀名的文件。 - - - -#### **flow** - -RTNCenteredTextNativeComponent.js - -```js -// @flow strict-local - -import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes"; -import type { HostComponent } from "react-native"; -import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; - -type NativeProps = $ReadOnly<{| - ...ViewProps, - text: ?string, - // 在这里添加其他属性 -|}>; - -export default (codegenNativeComponent( - "RTNCenteredText" -): HostComponent); -``` +以下是使用 Flow 和 TypeScript 声明的 RTNCenteredText 组件。在 `src` 目录中,创建一个命名为 `RTNCenteredText` 并带有相应后缀名的文件。(Flow 和 TypeScript 选择其一即可) #### **typescript** RTNCenteredTextNativeComponent.ts ```ts -import type { ViewProps } from "ViewPropTypes"; -import type { HostComponent } from "react-native"; -import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; +import type * as React from 'react'; +import type {ViewProps} from 'ViewPropTypes'; +import type {ColorValue, HostComponent} from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { + DirectEventHandler, + Int32, +} from 'react-native/Libraries/Types/CodegenTypes'; + +export type OnTouchEventData = Readonly<{ + type: Int32; +}>; export interface NativeProps extends ViewProps { text?: string; - // 添加其它 props + color?: ColorValue, + onTextTouch?: DirectEventHandler; + // 在这里添加其他 props } export default codegenNativeComponent( - "RTNCenteredText" + 'RTNCenteredText', ) as HostComponent; ``` -在声明文件的顶部导入了一些内容。以下是开发 Fabric 组件必须要导入的内容: - -- `HostComponent` 类型: 导出的组件需要与这个类型保持一致; -- `codegenNativeComponent` 函数:负责将组件注册到 JavaScript 运行时。 - 声明文件的中间部分包含了组件的 props。Props("properties" 的缩写)是用于自定义 React 组件的参数信息。在本例中,需要控制组件的 text 属性。 - -在声明文件的最后部分,导出了泛型函数 `codegenNativeComponent` 的返回值,此函数需要传递组件的名称。 - -### 3. Codegen 配置 - -#### 3.1 配置 `package.json` 文件 - -请在 `RTNCenteredText` 的根目录创建 `package.json` 文件。 - -```json -{ - "name": "rtn-centered-text", - "version": "0.0.1", - "description": "Showcase a Fabric component with a centered text", - "react-native": "src/index", - "source": "src/index", - "files": [ - "src", - "harmony", - "!**/__tests__", - "!**/__fixtures__", - "!**/__mocks__" - ], - "keywords": ["react-native", "harmony"], - "repository": "https://github.com//rtn-centered-text", - "author": " (https://github.com/)", - "license": "MIT", - "bugs": { - "url": "https://github.com//rtn-centered-text/issues" - }, - "homepage": "https://github.com//rtn-centered-text#readme", - "devDependencies": {}, - "peerDependencies": { - "react": "*", - "react-native": "*" - } -} -``` - -#### 3.2 选择 Fabric 的原生实现方式,配置 codegen - -RNOH 有特殊的架构限制,需要开发者在开发前根据需求选择好使用 ArkTS API 还是 CAPI 实现 Fabric。 - -关于如何选择,请阅读这篇[说明文档](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/capi-architecture.md)。 - -此步操作是要将 Codegen 的配置声明到 harmony.codegenConfig 字段。 - -- version: 枚举值,1 代表ArkTS组件的codegen版本,2 代表CAPI组件的codegen版本;(关于ArkTS和CAPI组件请见2.2的详细说明) -- specPaths:用于找到 js 接口声明文件的相对路径,它将被 Codegen 解析 - -> [!WARNING] 接入 codegen 之后,同一个模块中 ArkTS 版本和 CAPI 版本的 Fabric 无法共存,请先选择好实现方式 - -在 `package.json` 中新增 harmony.codegenConfig 字段: - -##### Option1: ArkTS API 实现 Fabric - -```json -{ - // ... - "harmony": { - "codegenConfig": { - "version": 1, - "specPaths": ["./src"] - } - } -} -``` - -##### Option2: C-API 实现 Fabric - -```json -{ - // ... - "harmony": { - "codegenConfig": { - "version": 2, - "specPaths": ["./src"] - } - } -} -``` - -#### 3.3 codegen通用配置项 - -HarmonyOS 需要在 RN 工程中通过运行脚本来执行 Codegen。 - -打开 RN 工程下的 package.json,如 `MyApp/package.json`,添加: - -```json -{ - ... - "scripts": { - ... - "codegen": "react-native codegen-harmony --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony" - }, - ... -} -``` - -> codegen-harmony 参数介绍: - -1. --rnoh-module-path: 指定 @rnoh/react-native-openharmony 模块的相对路径,用于存储生成的 ts 文件;如果使用 har 包引入 RNOH SDK,则需要指向安装之后的路径,比如:./harmony/entry/oh_modules/@rnoh/react-native-openharmony" - -2. --cpp-output-path: 指定用于存储生成的 C++ 文件的输出目录的相对路径,默认 ./harmony/entry/src/main/cpp/generated; - -3. --project-root-path: 包根目录的相对路径。 - -#### 3.4 codegen执行,生成胶水代码 - -首先,需要将包含模块的 NPM 包添加到 App。请确保 package.json 已经配置安装好以下依赖: - -```json -{ - ... - "dependencies": { - "react-native-harmony": "x.x.x", - ... - }, - "overrides": { - "@react-native/codegen": "0.74.0" - }, - ... -} -``` - -执行以下操作,假设 MyApp 为您的 App 工程路径 - -```bash -// 进入模块工程 -cd RTNCenteredText - -// 打包模块 -npm pack - -// 进入 App 工程 -cd ../MyApp - -// 本地路径安装模块 -npm i file:../RTNCenteredText/rtn-centered-text-0.0.1.tgz - -// 执行以下命令执行 codegen (HarmonyOS only) - -npm run codegen - -``` - -此命令会将 RTNCenteredText 模块添加到 App 内的 node_modules 目录。 - -执行codegen命令后在harmony工程得到如下目录结构 -```md -harmony\entry\src\main\cpp\generated -├──RNOHGeneratedPackage.h -└──rtn_centered_text - ├──react - │ └──renderer - │ └──components - │ └──rtn_centered_text - │ ├──ComponentDescriptors.h - │ ├──EventEmitters.cpp - │ ├──EventEmitters.h - │ ├──Props.cpp - │ ├──Props.h - │ ├──ShadowNodes.cpp - │ ├──ShadowNodes.h - │ ├──States.cpp - │ └──States.h - │ - └──RNOH - └──generated - ├──BaseRtnCenteredTextPackage.h - └──components - ├──BaseRTNCenteredTextComponentInstance.h - └──RTNCenteredTextJSIBinder.h -``` - -// TODO 需添加各文件作用描述 - -### 4. 实现原生组件 - -#### Option1: 使用 ArkTS API 实现原生组件 - -HarmonyOS 平台中 ArkTS 版本的 Fabric 组件的原生代码必须包含以下三个部分: - -1. 创建用于实现组件的 RTNCenteredText.ets -2. 创建 RTNCenteredTextPackage.ts -3. 创建用于导出模块的 index.ets 和 ts.ts -4. 修改 oh-package.json5,hvigorfile.ts,module.json5 - -> [!TIP] 可以在 DevEco Studio 中通过 File -> New -> Module.. -> Static Lirbrary 创建空壳模块,以此为基础修改文件内容 - -HarmonyOS 原生代码文件结构应为如下: - -```md -harmony -└── rtn_centered_text - ├── src - │ └── main - │ ├──ets - | | ├── RTNCenteredTextPackage.ts - │ │ └── RTNCenteredText.ets - │ └── module.json5 - ├── build-profile.json5 - ├── hvigorfile.ts - ├── index.ets - ├── oh-package.json5 - └── ts.ts -``` - -#### **RTNCenteredText.ets** - -```ts -import { RNComponentContext, RNViewBase } from '@rnoh/react-native-openharmony'; -// import codegen 生成的内容 -import { RNC } from "@rnoh/react-native-openharmony/generated"; - -@Component -export struct RTNCenteredText { - public static readonly NAME = RNC.RTNCenteredText.NAME - public ctx!: RNComponentContext - public tag: number = 0 - @State private descriptorWrapper: RNC.RTNCenteredText.DescriptorWrapper = {} as RNC.RTNCenteredText.DescriptorWrapper - private eventEmitter: RNC.RTNCenteredText.EventEmitter | undefined = undefined - private cleanUpCallbacks: (() => void)[] = [] - - aboutToAppear() { - this.eventEmitter = new RNC.RTNCenteredText.EventEmitter(this.ctx.rnInstance, this.tag) - this.onDescriptorWrapperChange(this.ctx.descriptorRegistry.findDescriptorWrapperByTag(this.tag)!) - this.cleanUpCallbacks.push(this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, - (_descriptor, newDescriptorWrapper) => { - this.onDescriptorWrapperChange(newDescriptorWrapper! as RNC.RTNCenteredText.DescriptorWrapper) - // 组件属性更新时进入的回调 - } - )) - } - - private onDescriptorWrapperChange(descriptorWrapper: RNC.RTNCenteredText.DescriptorWrapper) { - this.descriptorWrapper = descriptorWrapper - } - - aboutToDisappear() { - this.cleanUpCallbacks.forEach(cb => cb()) - } - - build() { - RNViewBase({ ctx: this.ctx, tag: this.tag }) { - Text(this.descriptorWrapper.props.text) - .height("100%") - .width("100%") - .fontSize(30) - .textAlign(TextAlign.Center) - } - } -} -``` - - +#### **flow** - +RTNCenteredTextNativeComponent.js -#### **RTNCenteredPackage.ts** +```js +// @flow strict-local -```ts -import { RNPackage } from "@rnoh/react-native-openharmony/ts"; +import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; +import type {HostComponent} from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; import type { - DescriptorWrapperFactoryByDescriptorTypeCtx, - DescriptorWrapperFactoryByDescriptorType, -} from "@rnoh/react-native-openharmony/ts"; -// import codegen 生成的内容 -import { RNC } from "@rnoh/react-native-openharmony/generated/ts"; - -export class RTNCenteredTextPackage extends RNPackage { - createDescriptorWrapperFactoryByDescriptorType( - ctx: DescriptorWrapperFactoryByDescriptorTypeCtx - ): DescriptorWrapperFactoryByDescriptorType { - return { - [RNC.RTNCenteredText.NAME]: (ctx) => - new RNC.RTNCenteredText.DescriptorWrapper(ctx.descriptor), - }; - } -} -``` - - - -创建 `ts.ts` - - - -#### **ts.ts** - -```ts -export * from "./src/main/ets/RTNCenteredTextPackage"; -``` + DirectEventHandler, + Int32, +} from 'react-native/Libraries/Types/CodegenTypes'; - - -创建 `index.ets` - - - -#### **index.ets** - -```ts -export * from "./ts"; -export * from "./src/main/ets/RTNCenteredText"; -``` - - - -#### Option2: 使用 C-API 实现原生组件 - -HarmonyOS 平台中 C-API 版本的 Fabric 组件的原生代码必须包含以下部分: - -1. 创建用于实现组件的 RTNCenteredTextComponentInstance.h、RTNCenteredTextComponentInstance.cpp; -2. 创建用于对接 ArkUI 的 xxxNode.h、xxxNode.cpp;(若框架已经实现相关的 Node,此步可以跳过。本例中需要用到的 TextNode、StackNode 框架已经实现,故无需自行创建) -3. Cpp 脚手架(请看 codegen 章节) -4. 修改 oh-package.json5,hvigorfile.ts,module.json5,build-profile.json5 - -> [!TIP] 可以在 DevEco Studio 中通过 File -> New -> Module.. -> Static Lirbrary 创建空壳模块,以此为基础修改文件内容 - -HarmonyOS 原生代码文件结构应为如下: - -```md -harmony -└── rtn_centered_text - ├── src - │ └── main - │ ├── cpp - │ │ ├── CMakeLists.txt - │ │ ├── CenteredTextPackage.h - │ │ ├── RTNCenteredTextComponentInstance.h - │ │ └── RTNCenteredTextComponentInstance.cpp - │ └── modules.json5 - ├── build-profile.json5 - ├── hvigorfile.ts - └── oh-package.json5 -``` - -创建 `RTNCenteredTextComponentInstance.h` - - - -#### **RTNCenteredTextComponentInstance.h** - -```cpp -#pragma once -#include "RNOH/generated/components/BaseRTNCenteredTextComponentInstance.h" -#include "RNOH/arkui/StackNode.h" -#include "RNOH/arkui/TextNode.h" - -namespace rnoh { -class RTNCenteredTextComponentInstance : public BaseRTNCenteredTextComponentInstance { -private: - using FragmentTouchTargetByTag = std::unordered_map>; - - using Super = BaseRTNCenteredTextComponentInstance; - TextNode m_textNode{}; - StackNode m_stackNode{}; - -public: - RTNCenteredTextComponentInstance(Context context); - StackNode &getLocalRootArkUINode() override; - -protected: - void onPropsChanged(SharedConcreteProps const &props) override; -}; -} // namespace rnoh -``` -这里的BaseRTNCenteredTextComponentInstance.h位于entry/src/main/cpp/generated/components目录下,通过后续的codegen命令自动生成。 - - -方法介绍: - -onPropsChanged:当 React Native 端的组件属性发生变化时(例如,文本内容、样式等),这个变化会通过桥接层传递到原生端。onPropsChanged 方法就是用来接收这些变化并作出相应处理的。 - -创建 `RTNCenteredTextComponentInstance.cpp` - - - -#### **RTNCenteredTextComponentInstance.cpp** - -```cpp -#include "RTNCenteredTextComponentInstance.h" - -namespace rnoh { - -RTNCenteredTextComponentInstance::RTNCenteredTextComponentInstance(Context context) - : Super(std::move(context)) { - m_stackNode.insertChild(m_textNode, 0); -} - -StackNode &RTNCenteredTextComponentInstance::getLocalRootArkUINode() { return m_stackNode; } - -void RTNCenteredTextComponentInstance::onPropsChanged(SharedConcreteProps const &props) { - CppComponentInstance::onPropsChanged(props); - if (props == nullptr) { - return; - } - m_textNode.setTextContent(props->text); - m_textNode.setFontSize(30.0); - m_textNode.setAlignment(ARKUI_ALIGNMENT_CENTER); -} - -} // namespace rnoh -``` - - - -修改 `RTNCenteredTextPackage.h` - - - -#### **RTNCenteredTextPackage.h** - -```cpp -#include "RNOH/Package.h" -#include "RNOH/generated/BaseRtnCenteredTextPackage.h" -#include "RTNCenteredTextComponentInstance.h" - -using namespace rnoh; -using namespace facebook; - -namespace rnoh { - -class RTNCenteredTextPackage : public BaseRtnCenteredTextPackage { -private: - using Super = BaseRtnCenteredTextPackage; -public: - RTNCenteredTextPackage(Package::Context ctx): BaseRtnCenteredTextPackage(ctx) {} - - ComponentInstance::Shared createComponentInstance(const ComponentInstance::Context& ctx) { - if (ctx.componentName == "RTNCenteredText") { - return std::make_shared(ctx); - } - return nullptr; - }; - -}; -} // namespace rnoh -``` - - - -修改 `CMakeLists.txt` - - - -#### **CMakeLists.txt** - -```txt -set(rtn_centered_text_generated_dir "${RNOH_GENERATED_DIR}/rtn_centered_text") - -file(GLOB_RECURSE rtn_centered_text_generated_SRC "${rtn_centered_text_generated_dir}/**/*.cpp") - -file(GLOB rtn_centered_text_SRC CONFIGURE_DEPENDS *.cpp) - -add_library(rtn_centered_text SHARED ${rtn_centered_text_SRC} ${rtn_centered_text_generated_SRC}) - -target_include_directories(rtn_centered_text PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${rtn_centered_text_generated_dir}) - -target_link_libraries(rtn_centered_text PUBLIC rnoh) -``` - - - -#### ArkTS Fabric 和 C-API Fabric 共有部分 - -修改 `oh-package.json5`,`hvigorfile.ts`,`module.json5`,`build-profile.json5`,或自行创建 - - - -#### **oh-package.json5** - -```json -{ - "license": "ISC", - "types": "", - "devDependencies": {}, - "name": "rtn-centered-text", - "description": "", - "main": "index.ets", // 仅 ArkTs 的 Fabric 需要 - "version": "0.0.1", - "dependencies": { - "@rnoh/react-native-openharmony": "file:../react_native_openharmony.har" - } -} -``` - - - - - -#### **hvigorfile.ts** - -```ts -export { harTasks } from "@ohos/hvigor-ohos-plugin"; -``` - - - - - -#### **module.json5** - -```json -{ - "module": { - "name": "rtn_centered_text", - "type": "har", - "deviceTypes": ["default"] - } -} -``` - - - - - -#### **build-profile.json5** - -```json -{ - "apiType": "stageMode", - "targets": [ - { - "name": "default", - "runtimeOS": "HarmonyOS" - } - ] -} -``` - - - -### 5. 将 Fabric 组件添加到 App - -#### 5.1 配置 RN 工程,进行打包安装 - -执行以下操作,假设 MyApp 为您的 App 工程路径 - -```bash -// 进入模块工程 -cd RTNCenteredText - -// 打包模块 -npm pack - -// 进入 App 工程 -cd ../MyApp - -// 本地路径安装模块 -npm i file:../RTNCenteredText/rtn-centered-text-0.0.1.tgz - -``` - -此命令会将 RTNCenteredText 模块添加到 App 内的 node_modules 目录。 - -#### 5.2 原生工程配置项 - -> [!tip] 待完善能力:HarmonyOS 平台目前暂时不支持 AutoLink,所以需要自行配置。 - -首先使用 DevEco Studio 打开 React-Native 项目里的鸿蒙工程 `harmony` - -目前 HarmonyOS 工程暂不支持引入工程外的模块,所以需要手动将模块的 HarmonyOS 源码复制到工程内。 - -- 复制 `RTNCenteredText/harmony/rtn_centered_text` 到 `harmony` 工程根目录下。 - -- 修改 `MyApp/harmony/build-profile.json5`,在 modules 字段添加: - -```json -{ -... - modules: [ - ... - { - name: 'rtn_centered_text', - srcPath: './rtn_centered_text', - } - ] -} -``` -使得 rtn_centered_text 能够被识别为模块,当文件夹右下角出现蓝色小方块时,说明识别成功。 - -- 在工程根目录的 `MyApp/harmony/oh-package.json5` 添加 overrides 字段 - -```json -{ - ... - "overrides": { - "@rnoh/react-native-openharmony" : "./react_native_openharmony.har" // RNOH SDK har包路径或源码路径 - } -} -``` - -- 打开 `MyApp/harmony/entry/oh-package.json5`,添加以下依赖,引入鸿蒙原生端的代码 - -```json -"dependencies": { - "@rnoh/react-native-openharmony": "file:../react_native_openharmony.har", // RNOH SDK har包路径或源码路径 - "rtn-centered-text": "file:../rtn_centered_text" - } -``` - -- 点击右上角的 `sync` 按钮同步工程,或在终端运行以下命令 - -```bash -cd entry -ohpm install -``` - -##### 5.2.1 ArkTS 组件特有配置项 - -打开 `MyApp/harmony/entry/src/main/ets/pages/Index.ets`,添加: - -```diff -... -+ import { RTNCenteredText } from "rtn-centered-text" - -+ const arkTsComponentNames: Array = [RTNCenteredText.NAME]; -@Builder -export function buildCustomRNComponent(ctx: ComponentBuilderContext) { - Stack() { -+ if (ctx.componentName === RTNCenteredText.NAME) { -+ RTNCenteredText({ -+ tag: ctx.tag, -+ ctx: ctx.rnComponentContext, -+ }) -+ } - //... - } - .position({x: 0, y: 0}) -} -//... -``` - -打开 `MyApp/harmony/entry/src/main/ets/RNPackageFactory.ts`,添加: - -```diff -import type {RNPackageContext, RNPackage} from '@rnoh/react-native-openharmony/ts'; -+ import { RTNCenteredTextPackage } from "rtn-centered-text/ts"; - -export function createRNPackages(ctx: RNPackageContext): RNPackage[] { - return [ -+ new RTNCenteredTextPackage(ctx), - ]; -} -``` - -编译、运行即可。 - -##### 5.2.2 C-API 组件特有配置项 - -打开 `MyApp/harmony/entry/src/main/cpp/CMakeLists.txt`,添加: - -```diff -project(rnapp) -cmake_minimum_required(VERSION 3.4.1) -set(CMAKE_SKIP_BUILD_RPATH TRUE) -set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") -set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules") -+ set(OH_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules") -+ set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated") -set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp") -set(LOG_VERBOSITY_LEVEL 1) -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") -set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use -add_compile_definitions(WITH_HITRACE_SYSTRACE) - -add_subdirectory("${RNOH_CPP_DIR}" ./rn) - -# RNOH_BEGIN: manual_package_linking_1 -add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package) -+ add_subdirectory("${OH_MODULES_DIR}/rtn-centered-text/src/main/cpp" ./centered-text) -# RNOH_END: manual_package_linking_1 - -file(GLOB GENERATED_CPP_FILES "./generated/*.cpp") - -add_library(rnoh_app SHARED - ${GENERATED_CPP_FILES} - "./PackageProvider.cpp" - "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp" -) -target_link_libraries(rnoh_app PUBLIC rnoh) - -# RNOH_BEGIN: manual_package_linking_2 -target_link_libraries(rnoh_app PUBLIC rnoh_sample_package) -+ target_link_libraries(rnoh_app PUBLIC rtn_centered_text) -# RNOH_END: manual_package_linking_2 -``` - -打开 `MyApp/harmony/entry/src/main/cpp/PackageProvider.cpp`,添加: - -```diff -#include "RNOH/PackageProvider.h" -#include "SamplePackage.h" -+ #include "CenteredTextPackage.h" -+ #include "generated/RNOHGeneratedPackage.h" -using namespace rnoh; - -std::vector> PackageProvider::getPackages(Package::Context ctx) { - return { - std::make_shared(ctx), - std::make_shared(ctx), -+ std::make_shared(ctx), - }; -} -``` - -编译、运行即可。 - -#### 5.3 JavaScript - -最后,操作以下步骤,您就可以在 JavaScript 调用组件了。 +export type OnTouchEventData = $ReadOnly<{| + type: Int32, +|}>; -1. 在 js 文件中导入组件。假设要在 App.js 进行导入,需要添加这行代码: +export type NativeProps = $ReadOnly<{| + ...ViewProps, + text?: string, + color?: ColorValue, + onTextTouch?: DirectEventHandler, + // add other props here +|}>; -```js -import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; +export default (codegenNativeComponent( + 'RTNCenteredText', +): HostComponent); ``` -2. 接下来,在 React Native 组件里进行调用。调用的语法和其它组件相同: - -**App.js** +在声明文件的顶部导入了一些内容。以下是开发 Fabric 组件必须要导入的内容: - +- `HostComponent` 类型: 导出的组件需要与这个类型保持一致; +- `codegenNativeComponent` 函数:负责将组件注册到 JavaScript 运行时。 + 声明文件的中间部分包含了组件的 **props**。Props("properties" 的缩写)是用于自定义 React 组件的参数信息。在本例中,需要控制组件的 `text` 属性。 -#### **App.js** +在声明文件的最后部分,导出了泛型函数 `codegenNativeComponent` 的返回值,此函数需要传递组件的名称。 -```js -/** - * Sample React Native App - * https://github.com/facebook/react-native - * - * @format - * @flow strict-local - */ -import React from "react"; -import type { Node } from "react"; -import { SafeAreaView } from "react-native"; -import RTNCenteredText from "rtn-centered-text/src/RTNCenteredTextNativeComponent"; - -const App: () => Node = () => { - // ... other App code ... - return ( - - - - ); -}; - -export default App; -``` +将编写好的声明类型代码文件文件放入 `RTNCenteredText/src` - +### 3. 选择 Fabric 的原生实现方式(重要),然后开发原生代码 -现在,您可以运行 App 并查看在屏幕上显示的组件。 +RNOH 有特殊的架构限制,需要开发者在开发前根据需求选择好使用 ArkTS API 还是 C-API 实现 Fabric。 -> [!TIP] 可通过 npm run start 使用热更新 +关于如何选择,请阅读这篇[说明文档](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/capi-architecture.md)。 -#### 5.4 如何将 HarmonyOS 原生部分的代码打成 *.har 包 -当完成上述操作,调试验证完毕您所需要的功能开发后,即可进行模块 har 打包操作 +`注意:开发容器组件目前只支持使用 CAPI 实现。` -1. 执行 Build/Make Module 'rtn_centered_text' +请根据实际情况选择: -2. 查看目录 rtn_centered_text/build/default/outputs/default (模块 har 包生成的默认路径) +- [使用 ArkTS API 开发 Fabric](/zh-cn/fabric-arkts.md) +- [使用 C-API 开发 Fabric](/zh-cn/fabric-capi.md) -3. 将上一步操作中所生成的 *.har 文件复制到最开始构建的三方库 RTNCenteredText 下的 harmony 文件夹下 -```md -RTNCenteredText - └──harmony - ├──rtn_centered_text - └──rtn_centered_text.har -``` \ No newline at end of file