Fabric 鸿蒙原生组件自定义实现说明
Fabric 鸿蒙原生组件自定义实现说明
本文档基于项目中的 FButton 组件实现,详细介绍如何在 React Native OpenHarmony 平台上开发 Fabric 架构的自定义原生组件。
目录
概述
Fabric 是 React Native 的新架构,它提供了更高效的渲染机制和更好的类型安全。在 OpenHarmony 平台上,自定义原生组件需要实现以下部分:
- TypeScript 规范:定义组件的接口和属性
- C++ 层实现:Props、EventEmitter、ComponentDescriptor
- 绑定层实现:JSIBinder、NapiBinder
- 事件处理:EventEmitRequestHandler
- ArkTS 组件:实际的 UI 实现
- Package 注册:将组件注册到系统中
前置条件
- React Native 0.77.1 或更高版本
- OpenHarmony 开发环境
- 已配置 Codegen 工具
- 了解 C++、TypeScript 和 ArkTS 基础语法
开发步骤
步骤1:定义 TypeScript 规范
在 src/components/ 目录下创建组件的 TypeScript 规范文件,例如 FButtonNativeComponent.tsx:
// 导入 React Native 的基础类型和工具
import {HostComponent, ViewProps} from 'react-native';
// DirectEventHandler 用于定义直接事件处理器,事件会同步传递给 JavaScript 层
import type {DirectEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
// codegenNativeComponent 是 Codegen 工具的核心函数,用于生成原生组件绑定代码
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
// 定义事件数据结构 - 描述从原生层传递到 JavaScript 层的事件数据格式
type OnFButtonClickEventData = Readonly<{
name: string; // 事件中包含的 name 字段
}>;
// 定义组件属性接口 - 继承 ViewProps 获得所有 View 的基础属性(如 style、onLayout 等)
interface FButtonProps extends ViewProps {
buttonText: string; // 必填属性:按钮显示的文本
onButtonClick?: DirectEventHandler<OnFButtonClickEventData> // 可选属性:按钮点击事件处理器
}
// 导出组件 - codegenNativeComponent 会根据这个规范自动生成 C++ 绑定代码
// 'FButton' 是组件名称,必须与后续 C++ 和 ArkTS 中的组件名称保持一致
export const FButton = codegenNativeComponent<FButtonProps>('FButton') as HostComponent<FButtonProps>;
关键点说明:
ViewProps:继承自 React Native 的 View 属性,提供基础的样式和布局属性DirectEventHandler:用于定义直接事件处理器,事件会直接传递给 JavaScript 层codegenNativeComponent:Codegen 工具会基于此规范生成 C++ 代码- 组件名称
'FButton'必须与后续 C++ 和 ArkTS 中的组件名称保持一致
步骤2:配置 Codegen
在 package.json 中配置 Codegen:
{
"harmony": {
"alias": "rtn-calculator",
"codegenConfig": [
{
"version": 2,
"specPaths": [
"src/specs"
]
}
]
}
}
注意: 如果组件规范文件在 src/components/ 目录下,需要确保 Codegen 能够扫描到该目录。通常 Codegen 会自动扫描项目中的 codegenNativeComponent 调用。
步骤3:实现 C++ Props
在 harmony/entry/src/main/cpp/ 目录下创建 FButtonProps.h:
#pragma once // 防止头文件被重复包含
// JSI (JavaScript Interface) 用于 JavaScript 和 C++ 之间的通信
#include <jsi/jsi.h>
// ViewProps 提供所有 View 组件的基础属性(样式、布局等)
#include <react/renderer/components/view/ViewProps.h>
// PropsParserContext 用于解析属性时的上下文信息
#include <react/renderer/core/PropsParserContext.h>
namespace facebook {
namespace react {
// FButtonProps 类:定义组件的属性结构
// final 关键字表示该类不能被继承
// 继承自 ViewProps 获得所有 View 的基础属性
class JSI_EXPORT FButtonProps final : public ViewProps {
public:
// 默认构造函数
FButtonProps() = default;
// 带参数的构造函数:从 RawProps 中解析属性值
// context: 属性解析上下文
// sourceProps: 源属性对象(用于属性合并)
// rawProps: 原始属性对象(从 JavaScript 层传递过来的原始数据)
FButtonProps(const PropsParserContext &context,
const FButtonProps &sourceProps,
const RawProps &rawProps);
#pragma mark - FButtonProps
// 自定义属性:按钮文本,默认值为空字符串
std::string buttonText{""};
};
}
}
创建对应的实现文件 FButtonProps.cpp:
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/PropsParserContext.h>
// propsConversions.h 提供了属性转换工具函数
#include <react/renderer/core/propsConversions.h>
#include "FButtonProps.h"
namespace facebook {
namespace react {
// FButtonProps 构造函数的实现
FButtonProps::FButtonProps(
const PropsParserContext &context, // 属性解析上下文
const FButtonProps &sourceProps, // 源属性(用于属性继承和合并)
const RawProps &rawProps // 原始属性(从 JavaScript 传递的原始数据)
): ViewProps(context, sourceProps, rawProps), // 先初始化父类 ViewProps
// convertRawProp 函数从 rawProps 中提取 "buttonText" 属性
// 参数说明:
// - context: 解析上下文
// - rawProps: 原始属性对象
// - "buttonText": 要提取的属性名称(必须与 TypeScript 规范中的属性名一致)
// - sourceProps.buttonText: 如果 rawProps 中没有该属性,使用源属性的值
// - {""}: 如果源属性也没有,使用默认值空字符串
buttonText(convertRawProp(context, rawProps, "buttonText", sourceProps.buttonText, {""}))
{}
}
}
关键点说明:
FButtonProps继承自ViewProps,获得所有 View 的基础属性convertRawProp用于从RawProps中提取并转换属性值- 最后一个参数
{""}是默认值 - 所有属性都需要在头文件中声明,并在构造函数中初始化
步骤4:实现 C++ EventEmitter
创建 FButtonEventEmitter.h:
#pragma once // 防止头文件重复包含
// ViewEventEmitter 提供事件发送的基础功能
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
// FButtonEventEmitter 类:负责向 JavaScript 层发送事件
// 继承自 ViewEventEmitter 获得基础的事件发送能力
class JSI_EXPORT FButtonEventEmitter : public ViewEventEmitter {
public:
// 使用父类的构造函数
using ViewEventEmitter::ViewEventEmitter;
// 定义 OnButtonClick 事件的数据结构
struct OnButtonClick {
std::string name; // 事件数据:按钮名称
};
// 发送按钮点击事件的方法
// value: 包含事件数据的结构体
// const 表示该方法不会修改对象状态
void onButtonClick(OnButtonClick value) const;
};
}
}
创建对应的实现文件 FButtonEventEmitter.cpp:
#include "FButtonEventEmitter.h"
namespace facebook {
namespace react {
// onButtonClick 方法的实现:将事件发送到 JavaScript 层
void facebook::react::FButtonEventEmitter::onButtonClick(OnButtonClick event) const {
// dispatchEvent 是父类提供的方法,用于向 JavaScript 层发送事件
// 第一个参数 "onButtonClick" 是事件名称,必须与 TypeScript 规范中的事件名称一致
// 第二个参数是一个 lambda 函数,用于构建事件数据对象
dispatchEvent("onButtonClick", [event = std::move(event)](jsi::Runtime &runtime) {
// 创建一个 JSI Object 作为事件数据载体
auto payload = jsi::Object(runtime);
// 将事件数据添加到 payload 中
// setProperty 的参数:runtime、属性名、属性值
payload.setProperty(runtime, "name", event.name);
// 返回构建好的事件数据对象,这个对象会被传递给 JavaScript 层的 onButtonClick 处理器
return payload;
});
}
}
}
关键点说明:
FButtonEventEmitter继承自ViewEventEmitterdispatchEvent用于向 JavaScript 层发送事件- 事件名称
"onButtonClick"必须与 TypeScript 规范中的事件名称一致 jsi::Object用于构建事件数据,传递给 JavaScript 层
步骤5:实现 ComponentDescriptor
创建 FButtonComponentDescriptor.h:
#pragma once
// ConcreteComponentDescriptor 用于描述组件的元信息
#include <react/renderer/core/ConcreteComponentDescriptor.h>
// ConcreteViewShadowNode 是 Fabric 架构中的 ShadowNode,用于管理组件的渲染树
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include "FButtonEventEmitter.h"
#include "FButtonProps.h"
namespace facebook{
namespace react {
// 组件名称常量,必须与 TypeScript 规范中的组件名称一致
extern const char FButtonComponentName[] = "FButton";
// 定义 FButtonShadowNode 类型别名
// ConcreteViewShadowNode 是模板类,需要三个参数:
// 1. 组件名称
// 2. Props 类型(FButtonProps)
// 3. EventEmitter 类型(FButtonEventEmitter)
// ShadowNode 是 Fabric 架构的核心,用于管理组件的状态和属性
using FButtonShadowNode = ConcreteViewShadowNode<FButtonComponentName, FButtonProps, FButtonEventEmitter>;
// 定义 FButtonComponentDescriptor 类型别名
// ComponentDescriptor 用于描述组件的元信息,系统通过它来创建和管理组件实例
using FButtonComponentDescriptor = ConcreteComponentDescriptor<FButtonShadowNode>;
}
}
关键点说明:
FButtonComponentName必须与 TypeScript 规范中的组件名称一致ConcreteViewShadowNode是 Fabric 架构中的 ShadowNode,用于管理组件的渲染树ConcreteComponentDescriptor用于描述组件的元信息
步骤6:实现 JSIBinder
创建 FButtonJSIBinder.h:
#pragma once
// ViewComponentJSIBinder 提供 View 组件的 JSI 绑定基础功能
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
namespace rnoh {
// FButtonJSIBinder 类:负责在 JSI (JavaScript Interface) 层绑定组件
// JSI 是 React Native 新架构中 JavaScript 和 C++ 之间的直接通信接口
class FButtonJSIBinder : public ViewComponentJSIBinder {
// 重写 createNativeProps 方法:定义组件的属性类型信息
// 这个方法返回一个 JSI Object,描述组件有哪些属性以及它们的类型
facebook::jsi::Object createNativeProps(facebook::jsi::Runtime& rt) override {
// 先获取父类(ViewComponentJSIBinder)定义的属性
auto object = ViewComponentJSIBinder::createNativeProps(rt);
// 添加自定义属性 "buttonText",类型为 "string"
// 这个属性名必须与 TypeScript 规范中的属性名一致
object.setProperty(rt, "buttonText", "string");
return object;
}
// 重写 createDirectEventTypes 方法:定义组件支持的事件类型
// 直接事件(Direct Event)会同步传递给 JavaScript 层
facebook::jsi::Object createDirectEventTypes(
facebook::jsi::Runtime& rt) override {
// 创建一个 JSI Object 来存储事件类型映射
facebook::jsi::Object events(rt);
// 事件名称映射规则:
// - JSI 层使用 "top" + 首字母大写的属性名格式:topButtonClick
// - JavaScript 层使用驼峰命名:onButtonClick
// createDirectEvent 创建一个直接事件类型描述
events.setProperty(rt, "topButtonClick", createDirectEvent(rt, "onButtonClick"));
return events;
}
};
} // namespace rnoh
关键点说明:
FButtonJSIBinder继承自ViewComponentJSIBindercreateNativeProps定义组件的属性类型,用于 JSI 绑定createDirectEventTypes定义直接事件类型- 事件名称格式:
"top" + 首字母大写的属性名,例如"topButtonClick"对应"onButtonClick" - 属性类型使用字符串表示,如
"string"、"number"、"boolean"等
步骤7:实现 NapiBinder
创建 FButtonNapiBinder.h:
// ViewComponentNapiBinder 提供 View 组件的 NAPI 绑定基础功能
// NAPI (Node-API) 是 OpenHarmony 中用于 C++ 和 ArkTS 之间通信的接口
#include "RNOHCorePackage/ComponentBinders/ViewComponentNapiBinder.h"
#include "FButtonProps.h"
namespace rnoh {
// FButtonNapiBinder 类:负责将 C++ 层的 Props 转换为 NAPI 值,传递给 ArkTS 层
class FButtonNapiBinder : public ViewComponentNapiBinder {
public:
// 重写 createProps 方法:将 C++ 的 Props 转换为 NAPI 值
// env: NAPI 环境对象
// shadowView: 包含组件属性和状态的 ShadowView 对象
napi_value createProps(napi_env env, facebook::react::ShadowView const shadowView) override {
// 先获取父类(ViewComponentNapiBinder)转换的基础 View 属性
napi_value napiViewProps = ViewComponentNapiBinder::createProps(env, shadowView);
// 尝试将 shadowView.props 转换为 FButtonProps 类型
// std::dynamic_pointer_cast 是安全的类型转换,如果类型不匹配会返回 nullptr
if (auto props = std::dynamic_pointer_cast<const facebook::react::FButtonProps>(shadowView.props)) {
// 使用 ArkJS 工具类简化 NAPI 操作
// getObjectBuilder: 获取对象构建器,用于添加属性
// addProperty: 添加自定义属性 "buttonText",值为 props->buttonText
// build: 构建并返回最终的 NAPI 对象
return ArkJS(env)
.getObjectBuilder(napiViewProps)
.addProperty("buttonText", props->buttonText)
.build();
}
// 如果类型转换失败,返回基础的 View 属性
return napiViewProps;
};
};
} // namespace rnoh
关键点说明:
FButtonNapiBinder继承自ViewComponentNapiBindercreateProps方法将 C++ 的 Props 转换为 NAPI 值,传递给 ArkTS 层- 使用
ArkJS工具类简化 NAPI 操作 - 需要检查
props的类型,确保类型安全
步骤8:实现 EventEmitRequestHandler
创建 FButtonEventEmitRequestHandler.h:
#pragma once
#include <napi/native_api.h> // NAPI 接口,用于 C++ 和 ArkTS 之间的通信
#include "FButtonEventEmitter.h"
#include "RNOH/ArkJS.h" // ArkJS 工具类,简化 NAPI 操作
#include "RNOH/EventEmitRequestHandler.h" // 事件处理基类
using namespace facebook;
namespace rnoh {
// 定义事件类型枚举,用于区分不同的事件
enum FButtonEventType {
FBUTTON_ON_BUTTON_CLICK = 0 // 按钮点击事件
};
// 解析事件类型的辅助函数
// arkJs: ArkJS 工具对象
// eventObject: 从 ArkTS 层传递过来的事件对象(NAPI 值)
// eventName: 事件名称(通常是组件名称)
FButtonEventType getFButtonEventType(ArkJS &arkJs, napi_value eventObject, std::string eventName) {
auto eventType = eventName;
LOG(INFO) << "getFButtonEventType = " + eventType;
// 首先检查事件名称是否是 "FButton"(组件名称)
if (eventType == "FButton") {
// 从事件对象中获取 "type" 字段,这个字段在 ArkTS 组件中设置
std::string type = arkJs.getString(arkJs.getObjectProperty(eventObject, "type"));
// 根据 type 字段判断具体的事件类型
if (type == "onButtonClick") {
return FButtonEventType::FBUTTON_ON_BUTTON_CLICK;
} else {
throw std::runtime_error("Unknown FButton event type");
}
} else {
throw std::runtime_error("Unknown component event type");
}
}
// FButtonEventEmitRequestHandler 类:处理从 ArkTS 层发送到 C++ 层的事件
// 当 ArkTS 组件调用 emitComponentEvent 时,事件会通过这个处理器传递到 C++ 层
class FButtonEventEmitRequestHandler : public EventEmitRequestHandler {
public:
// 重写 handleEvent 方法:处理事件
// ctx: 事件处理上下文,包含事件数据、组件 tag 等信息
void handleEvent(EventEmitRequestHandler::Context const &ctx) override {
// 创建 ArkJS 工具对象
ArkJS arkJs(ctx.env);
// 从 ShadowView 注册表中获取对应 tag 的 EventEmitter
// ctx.tag 是组件的唯一标识
auto eventEmitter = ctx.shadowViewRegistry->getEventEmitter<react::FButtonEventEmitter>(ctx.tag);
// 如果找不到 EventEmitter,直接返回(可能组件已被销毁)
if (eventEmitter == nullptr) {
return;
}
// 解析事件类型
// ctx.payload: 事件数据对象(从 ArkTS 层传递)
// ctx.eventName: 事件名称(通常是组件名称)
FButtonEventType type = getFButtonEventType(arkJs, ctx.payload, ctx.eventName);
// 根据事件类型处理不同的事件
switch (type) {
case FBUTTON_ON_BUTTON_CLICK: {
// 从事件数据中提取 "name" 字段
std::string name = arkJs.getString(arkJs.getObjectProperty(ctx.payload, "name"));
LOG(INFO) << "FButtonEventEmitter OnButtonClick " << name;
// 构建事件数据结构
react::FButtonEventEmitter::OnButtonClick event = {name};
// 调用 EventEmitter 的方法,将事件发送到 JavaScript 层
eventEmitter->onButtonClick(event);
break;
}
default:
break;
}
}
};
}
关键点说明:
EventEmitRequestHandler用于处理从 ArkTS 层发送到 C++ 层的事件getFButtonEventType函数解析事件类型,根据eventName和事件对象中的type字段判断handleEvent方法处理事件,从ctx.payload中提取事件数据,然后调用EventEmitter的方法ctx.shadowViewRegistry用于获取对应 tag 的 EventEmitter
步骤9:实现 ArkTS 组件
在 harmony/entry/src/main/ets/components/ 目录下创建 FButtom.ets(注意:文件名可以是 FButton.ets,项目中使用了 FButtom.ets):
// 导入 React Native OpenHarmony 的核心类型和组件
import { Descriptor, RNOHContext, RNViewBase, ViewBaseProps } from "@rnoh/react-native-openharmony";
// 定义组件的属性接口,继承 ViewBaseProps 获得基础属性
export interface FButtonProps extends ViewBaseProps {
buttonText: string; // 按钮显示的文本
}
// 定义组件描述符类型,Descriptor 是泛型类型,包含组件名称和属性类型
export type FButtonDescriptor = Descriptor<"FButton", FButtonProps>
// @Component 装饰器:标记这是一个 ArkTS 组件
@Component
export struct FButton {
// 组件名称常量,必须与 TypeScript 和 C++ 中的组件名称一致
static NAME: string = "FButton"
// RNOHContext: React Native OpenHarmony 的上下文对象,提供各种服务
// ! 表示这个属性必须被初始化
ctx!: RNOHContext;
// tag: 组件的唯一标识符,由系统分配,用于在注册表中查找组件
// 注意:实际使用时,tag 应该从外部传入,而不是硬编码为 101
tag: number = 101;
// 描述符变化监听器的取消订阅函数
private unregisterDescriptorChangeListener?: () => void = undefined;
// @State 装饰器:标记为状态变量,当值改变时会触发 UI 更新
// descriptor: 组件的描述符,包含 props、状态等信息
@State
private descriptor: FButtonDescriptor = {} as FButtonDescriptor
// buttonText: 按钮显示的文本,从 props 中提取
@State
private buttonText: string = "";
// bgColor: 按钮背景颜色,可以通过命令动态修改
@State
private bgColor: string = "#ffae00";
// aboutToAppear: 组件生命周期方法,在组件即将显示时调用
aboutToAppear(): void {
// 从描述符注册表中获取组件的描述符
// getDescriptor 根据 tag 查找对应的组件描述符
this.descriptor = this.ctx.descriptorRegistry.getDescriptor<FButtonDescriptor>(this.tag);
// 订阅描述符变化,当 JavaScript 层更新 props 时,会触发回调
// subscribeToDescriptorChanges 返回一个取消订阅的函数
this.unregisterDescriptorChangeListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, (newDescriptor) => {
// 更新描述符
this.descriptor = newDescriptor as FButtonDescriptor;
// 从新的描述符中提取 buttonText 属性并更新状态
// rawProps 是原始属性对象,包含从 JavaScript 层传递的所有属性
this.buttonText = (newDescriptor.rawProps as FButtonProps).buttonText;
});
// 初始化时从描述符中提取 buttonText 属性
this.buttonText = (this.descriptor.rawProps as FButtonProps).buttonText;
// 注册命令回调:接收来自 JavaScript 层的命令
// registerCommandCallback 的参数:
// - this.descriptor.tag: 组件的 tag(使用 descriptor.tag 而不是 this.tag)
// - 回调函数:接收命令名称和参数
// 当 JavaScript 调用 UIManager.dispatchViewManagerCommand 时,会触发这个回调
this.ctx.componentCommandReceiver.registerCommandCallback(this.descriptor.tag, (cmd, args: ESObject) => {
// 处理 "updateBgColor" 命令
if (cmd === "updateBgColor") {
// args 是一个数组,第一个元素是新的背景颜色值
this.bgColor = (args as Array<ESObject>)[0] as string;
}
})
}
// aboutToDisappear: 组件生命周期方法,在组件即将消失时调用
aboutToDisappear(): void {
// 取消订阅描述符变化监听,避免内存泄漏
this.unregisterDescriptorChangeListener?.();
}
// build: 组件的构建方法,定义组件的 UI 结构
build() {
// RNViewBase: React Native 的基础 View 组件,必须包裹自定义组件
// 它负责处理布局、样式等基础功能
RNViewBase({ ctx: this.ctx, tag: this.tag }) {
// 使用 ArkTS 的 Button 组件
Button(this.buttonText)
.width(200) // 设置按钮宽度
.height(48) // 设置按钮高度
.backgroundColor(this.bgColor) // 设置背景颜色(可以从命令动态修改)
.onClick(() => {
// 按钮点击事件处理
// emitComponentEvent: 向 React Native 层发送事件
// 参数说明:
// - this.descriptor.tag: 组件的 tag
// - "FButton": 组件名称(必须与 C++ 中的组件名称一致)
// - 事件数据对象:
// * type: 事件类型,必须与 EventEmitRequestHandler 中解析的类型一致
// * name: 事件数据,会传递给 JavaScript 层的 onButtonClick 处理器
this.ctx.rnInstance.emitComponentEvent(this.descriptor.tag, "FButton", {
type: "onButtonClick", // 事件类型
name: this.buttonText // 事件数据
})
})
}
}
}
关键点说明:
@Component装饰器标记这是一个 ArkTS 组件static NAME必须与 C++ 和 TypeScript 中的组件名称一致tag是组件的唯一标识,由系统分配descriptorRegistry用于获取和订阅组件的描述符(包含 props)componentCommandReceiver用于接收来自 JavaScript 的命令(如UIManager.dispatchViewManagerCommand)rnInstance.emitComponentEvent用于发送事件,第一个参数是组件名称,第二个参数是事件数据- 事件数据中的
type字段必须与EventEmitRequestHandler中解析的类型一致
步骤10:注册组件到 Package
创建或修改 FabricTurboModulePackage.h:
#pragma once
#include "RNOH/Package.h"
namespace rnoh {
class FabricTurboModulePackage : public Package {
public:
FabricTurboModulePackage(Package::Context ctx) : Package(ctx) {}
std::unique_ptr<TurboModuleFactoryDelegate> createTurboModuleFactoryDelegate() override ;
std::vector<facebook::react::ComponentDescriptorProvider> createComponentDescriptorProviders() override;
ComponentNapiBinderByString createComponentNapiBinderByName() override;
ComponentJSIBinderByString createComponentJSIBinderByName() override;
EventEmitRequestHandlers createEventEmitRequestHandlers() override;
};
}
实现 FabricTurboModulePackage.cpp:
#include "FabricTurboModulePackage.h"
#include "FButtonComponentDescriptor.h" // 组件描述符
#include "FButtonEventEmitRequestHandler.h" // 事件处理器
#include "FButtonJSIBinder.h" // JSI 绑定器
#include "FButtonNapiBinder.h" // NAPI 绑定器
#include "FabricTurboModuleSpec.h" // TurboModule 规范(如果需要)
using namespace rnoh;
using namespace facebook;
// TurboModule 工厂委托类(如果 Package 中需要包含 TurboModule)
// TurboModule 用于提供 JavaScript 可以直接调用的原生方法
class FabricTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
public:
// 创建 TurboModule 实例
// ctx: Package 上下文
// name: TurboModule 名称
SharedTurboModule createTurboModule(Context ctx, const std::string& name) const override {
// 根据名称创建对应的 TurboModule
if (name == "FabricTurboModule") {
return std::make_shared<NativeFabricTurboModuleSpecJSI>(ctx, name);
}
return nullptr; // 如果名称不匹配,返回 nullptr
}
};
// 实现 createTurboModuleFactoryDelegate 方法
// 返回 TurboModule 工厂委托实例
std::unique_ptr<TurboModuleFactoryDelegate> FabricTurboModulePackage::createTurboModuleFactoryDelegate() {
return std::make_unique<FabricTurboModuleFactoryDelegate>();
}
// 注册组件描述符提供者
// 这个方法告诉系统有哪些组件可以使用,系统会根据这个列表创建组件描述符
std::vector<facebook::react::ComponentDescriptorProvider> FabricTurboModulePackage::createComponentDescriptorProviders() {
return {
// concreteComponentDescriptorProvider 是一个模板函数,用于创建组件描述符提供者
// 它接收 ComponentDescriptor 类型作为模板参数
react::concreteComponentDescriptorProvider<react::FButtonComponentDescriptor>(),
// 如果有多个组件,可以在这里继续添加
};
}
// 注册事件处理器
// 当 ArkTS 组件发送事件时,系统会根据组件名称查找对应的事件处理器
EventEmitRequestHandlers rnoh::FabricTurboModulePackage::createEventEmitRequestHandlers() {
// 返回一个包含所有事件处理器的向量
// 每个事件处理器负责处理特定组件的事件
return { std::make_shared<FButtonEventEmitRequestHandler>() };
}
// 注册 JSI Binder
// JSI Binder 用于在 JSI 层绑定组件,定义组件的属性和事件类型
ComponentJSIBinderByString rnoh::FabricTurboModulePackage::createComponentJSIBinderByName() {
// 返回一个 map,key 是组件名称,value 是对应的 JSI Binder 实例
// 组件名称 "FButton" 必须与 TypeScript 规范中的组件名称一致
return { {"FButton", std::make_shared<FButtonJSIBinder>()} };
}
// 注册 NAPI Binder
// NAPI Binder 用于将 C++ 的 Props 转换为 NAPI 值,传递给 ArkTS 层
ComponentNapiBinderByString rnoh::FabricTurboModulePackage::createComponentNapiBinderByName() {
// 返回一个 map,key 是组件名称,value 是对应的 NAPI Binder 实例
return { {"FButton", std::make_shared<FButtonNapiBinder>()} };
}
关键点说明:
createComponentDescriptorProviders注册组件的描述符提供者createEventEmitRequestHandlers注册事件处理器createComponentJSIBinderByName注册 JSI 绑定器createComponentNapiBinderByName注册 NAPI 绑定器- 组件名称作为 key,必须与组件定义中的名称一致
步骤11:配置 CMakeLists.txt
在 harmony/entry/src/main/cpp/CMakeLists.txt 中添加组件的源文件:
# add_library: 定义一个库目标
# rnoh_app: 库的名称
# SHARED: 表示这是一个动态链接库(.so 文件)
add_library(rnoh_app SHARED
# 其他包的源文件(如果有)
${rtn_calculator_package_src}
${rtn_calculator_generated_dir_src}
# Package 提供者:负责注册所有的 Package
"./PackageProvider.cpp"
# NAPI 桥接文件:提供 C++ 和 ArkTS 之间的通信桥梁
"${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
# 添加 FButton 相关源文件
# 注意:只需要添加 .cpp 文件,头文件(.h)不需要添加
# 但需要确保头文件路径在 include_directories 中正确配置
"./FButtonProps.cpp" # Props 实现文件
"./FButtonEventEmitter.cpp" # EventEmitter 实现文件
"./FabricTurboModulePackage.cpp" # Package 实现文件
"./FabricTurboModuleSpec.cpp" # TurboModule 规范实现(如果需要)
)
关键点说明:
- 确保所有
.cpp文件都添加到add_library中 - 头文件不需要添加,只需要确保包含路径正确
步骤12:在 Index.ets 中注册组件构建器
在 harmony/entry/src/main/ets/pages/Index.ets 中注册组件构建器:
// 导入自定义的 ArkTS 组件
import { FButton } from '../components/FButtom';
// 定义需要自定义构建的组件名称列表
// 这个数组列出了所有需要由 ArkTS 实现的 React Native 组件
// 系统会根据这个列表决定哪些组件需要调用自定义构建器
const arkTsComponentNames: Array<string> = ["FButton"]
// @Builder 装饰器:标记这是一个构建器函数
// 构建器函数用于根据组件名称动态创建对应的 ArkTS 组件
@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {
// ctx.componentName: 当前要构建的组件名称
// ctx.tag: 系统为组件分配的唯一标识符
// ctx.rnComponentContext: React Native OpenHarmony 的上下文对象
// 根据组件名称判断应该创建哪个组件
if (ctx.componentName === FButton.NAME) {
// 创建 FButton 组件实例
// 传递必要的参数:
// - ctx: React Native OpenHarmony 上下文
// - tag: 组件的唯一标识符
FButton({
ctx: ctx.rnComponentContext,
tag: ctx.tag
})
}
// 如果有其他自定义组件,可以继续添加 else if 分支
}
// wrapBuilder: 将构建器函数包装成系统需要的格式
// 这个包装后的构建器会被传递给 RNApp
const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent)
然后在 RNApp 中使用:
RNApp({
// ... 其他配置(如 JSBundleProvider、packages 等)
// customComponentBuilder: 自定义组件构建器
// 当系统遇到 arkTsComponentNames 中列出的组件时,会调用这个构建器来创建组件
customComponentBuilder: wrappedCustomRNComponentBuilder,
// ... 其他配置
})
关键点说明:
arkTsComponentNames数组列出所有需要自定义构建的组件buildCustomRNComponent函数根据组件名称返回对应的 ArkTS 组件ctx.tag是系统分配的组件标识ctx.rnComponentContext是 React Native OpenHarmony 的上下文对象
步骤13:在 JavaScript 中使用
在 App.tsx 或任何 React 组件中使用:
import React, {useRef, useState} from 'react';
import {
UIManager, // UIManager 用于向原生组件发送命令
findNodeHandle, // findNodeHandle 用于获取组件的原生节点句柄
} from 'react-native';
import { FButton } from './src/components/FButtonNativeComponent';
function App(): React.JSX.Element {
// useRef: 创建一个 ref 对象,用于获取组件引用
// ref 是访问原生组件的唯一方式,用于发送命令
const fButtonRef = useRef(null);
// useState: 创建状态变量,用于存储按钮点击的结果
const [result, setResult] = useState("");
return (
<SafeAreaView>
<Text>Hello World {result}</Text>
{/* FButton 组件使用示例 */}
<FButton
buttonText='FButtonTest11111' // 设置按钮文本属性
ref={fButtonRef} // 绑定 ref,用于后续发送命令
onButtonClick={(e) => {
// 事件处理器:当按钮被点击时调用
// e.nativeEvent: 包含从原生层传递过来的事件数据
// e.nativeEvent.name: 事件数据中的 name 字段(在 EventEmitter 中设置)
console.log("Button clicked:", e.nativeEvent.name);
setResult(`click ${e.nativeEvent.name}`);
// 发送命令到原生组件
// dispatchViewManagerCommand 用于向原生组件发送命令
// 参数说明:
// - findNodeHandle(fButtonRef.current): 获取组件的原生节点句柄
// - "updateBgColor": 命令名称,必须在 ArkTS 组件的 registerCommandCallback 中注册
// - ["#009688"]: 命令参数数组,会传递给 ArkTS 组件的命令回调函数
UIManager.dispatchViewManagerCommand(
findNodeHandle(fButtonRef.current),
"updateBgColor",
["#009688"] // 新的背景颜色值
);
}}
/>
</SafeAreaView>
);
}
关键点说明:
- 使用
ref获取组件引用,用于发送命令 onButtonClick事件处理器接收nativeEvent对象UIManager.dispatchViewManagerCommand用于向原生组件发送命令- 命令名称
"updateBgColor"必须在 ArkTS 组件的registerCommandCallback中处理
完整代码示例
文件结构
project/
├── src/
│ └── components/
│ └── FButtonNativeComponent.tsx # TypeScript 规范
├── harmony/
│ └── entry/
│ └── src/
│ ├── main/
│ │ ├── cpp/
│ │ │ ├── FButtonProps.h/cpp
│ │ │ ├── FButtonEventEmitter.h/cpp
│ │ │ ├── FButtonComponentDescriptor.h
│ │ │ ├── FButtonJSIBinder.h
│ │ │ ├── FButtonNapiBinder.h
│ │ │ ├── FButtonEventEmitRequestHandler.h
│ │ │ ├── FabricTurboModulePackage.h/cpp
│ │ │ └── CMakeLists.txt
│ │ └── ets/
│ │ ├── components/
│ │ │ └── FButtom.ets
│ │ └── pages/
│ │ └── Index.ets
常见问题
1. 组件无法显示
可能原因:
- 组件名称不一致(TypeScript、C++、ArkTS 中的名称必须一致)
- 未在
Index.ets中注册组件构建器 arkTsComponentNames数组中未包含组件名称
解决方法:
- 检查所有文件中的组件名称是否一致
- 确保在
buildCustomRNComponent中正确返回组件 - 确保
RNApp配置了customComponentBuilder
2. 属性无法传递
可能原因:
NapiBinder中未正确转换属性- ArkTS 组件中未正确读取
descriptor.rawProps
解决方法:
- 检查
FButtonNapiBinder::createProps是否正确添加属性 - 检查 ArkTS 组件中是否正确从
descriptor.rawProps读取属性 - 确保订阅了描述符变化
3. 事件无法触发
可能原因:
- 事件名称不一致
EventEmitRequestHandler中事件类型解析错误JSIBinder中事件类型定义错误
解决方法:
- 检查 TypeScript 规范中的事件名称
- 检查
JSIBinder中的事件映射(topButtonClick->onButtonClick) - 检查
EventEmitRequestHandler中的事件类型判断逻辑 - 检查 ArkTS 组件中
emitComponentEvent的第一个参数(组件名称)和事件数据中的type字段
4. 命令无法执行
可能原因:
- 命令名称不一致
- ArkTS 组件中未注册命令回调
解决方法:
- 确保 JavaScript 中的命令名称与 ArkTS 中注册的命令名称一致
- 确保在
aboutToAppear中正确注册了命令回调 - 检查
tag是否正确传递
5. Codegen 未生成代码
可能原因:
package.json中未配置 Codegen- 规范文件路径不正确
- Codegen 命令未执行
解决方法:
- 检查
package.json中的harmony.codegenConfig配置 - 运行
npm run codegen-harmony生成代码 - 检查生成的文件是否在
harmony/entry/src/main/cpp/generated/目录下
6. 编译错误
可能原因:
- CMakeLists.txt 中未添加源文件
- 头文件包含路径错误
- 命名空间使用错误
解决方法:
- 检查
CMakeLists.txt中是否包含所有.cpp文件 - 检查
#include路径是否正确 - 确保命名空间使用正确(
facebook::react和rnoh)
总结
开发 Fabric 自定义组件需要实现多个层次的代码:
- TypeScript 层:定义组件接口和属性
- C++ 层:实现 Props、EventEmitter、ComponentDescriptor
- 绑定层:实现 JSIBinder 和 NapiBinder
- 事件层:实现 EventEmitRequestHandler
- UI 层:实现 ArkTS 组件
- 注册层:在 Package 中注册所有组件
每个层次都有其特定的职责,需要仔细实现和测试。建议按照本文档的步骤逐步实现,并在每个步骤后进行测试,确保功能正常。
参考资源
- React Native 新架构文档
- OpenHarmony 开发文档
- 项目中的 FButton 实现示例
更多推荐


所有评论(0)