往期知识点整理
- 鸿蒙(HarmonyOS)北向开发知识点记录~
- 【鸿蒙实战开发】har和hsp的转换
- 【鸿蒙实战开发】基于CameraKit对相机进行操作
- 【鸿蒙实战开发】基于HAR的跨模块C++头文件引用
- 【鸿蒙实战开发】基于原生实现高级显示效果
- 【鸿蒙实战开发】基于动态配置签名的多人协同开发应用签名解决方案
- 【鸿蒙实战开发】基于webview的Picker拉起
- 【鸿蒙实战开发】基于@ohos.data.unifiedDataChannel实现数据共享
- 【鸿蒙实战开发】Fabric 自定义组件开发指南
- 持续更新中……
1.编写 RN 调用 Fabric 组件的代码
编写MarqueeViewNativeComponent.tsx,注意,如果要使用 Codegen ,文件必须以NativeComponent命名。在文件中使用 codegenNativeComponent 创建 MarqueeView 组件,其中 MarqueeViewProps 里声明了 src 属性和 onStop 事件:
type OnStopEventData = Readonly<{
isStop: boolean
}>;
interface MarqueeViewProps extends ViewProps {
src: string,
onStop?: DirectEventHandler<OnStopEventData>;
}
const MarqueeView = codegenNativeComponent<MarqueeViewProps>(
'MarqueeView'
) as HostComponent<MarqueeViewProps>;
和其他标准组件的创建方式一样,在组件容器内添加 MarqueeView 标签:
<MarqueeView
src="双十一大促,消费是社会再生产过程中的一个重要环节,也是最终环节。它是指利用社会产品来满足人们各种需要的过程。"
style={{height: 180, width: '100%', backgroundColor: 'hsl(210, 80%, 50%)'}}
onStop={(e) => {
SampleTurboModule.rnLog("native调用了RN的 onStop,isStop = "+e.nativeEvent.isStop)
setMarqueeStop(e.nativeEvent.isStop)
}}
/>
2.编写ArkTS原生实现代码
Descriptor 的功能是封装 RN 侧组件代码传递到 ArkUI 组件的参数,MarqueeView 对 RN 侧公开了一个 src 参数,用于显示跑马灯的滚动内容。原生侧定义 MarqueeViewDescriptor 代码如下:
export interface MarqueeViewProps extends ViewBaseProps {
src: string
}
export type MarqueeViewDescriptor = Descriptor<"MarqueeView", MarqueeViewProps>;
Descriptor 不需要我们手动创建,由 rnoh 自动生成;组件 tag 也不需要我们手动设置,rnoh 会为组件自动分配 tag。开发者只需要通过 getDescriptor 方法获取对应 tag 的 Descriptor:
this.descriptor = this.ctx.descriptorRegistry.getDescriptor<MarqueeViewDescriptor>(this.tag)
当 RN 侧传递过来的属性参数发生变化时,我们需要更新 Descripotor:
this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, (newDescriptor) => {
this.descriptor = (newDescriptor as MarqueeViewDescriptor)
})
RN 调用原生方法
RN 侧调用 UIManager.dispatchViewManagerCommand 向原生发送消息:
UIManager.dispatchViewManagerCommand(
findNodeHandle(nativeRef.current),
'toggleMarqueeState',
[],
)
原生组件通过 commandDispatcher.registerCommandCallback 接收消息并执行对应方法:
this.ctx.commandDispatcher.registerCommandCallback(this.tag, (commandName) => {
if (commandName === "toggleMarqueeState") {
this.start = !this.start
console.log("will emitComponentEvent");
}
})
原生组件调用 RN 侧方法
RN 侧添加 onStop 方法实现:
<MarqueeView
...
onStop={(e) => {
// 原生组件调用了 RN 侧的 MarqueeView 的 onStop 方法
const isStop = e.nativeEvent.isStop
...
}}
/>
原生侧发送调用 RN 组件事件的消息:
this.ctx.rnInstance.emitComponentEvent(
this.descriptor.tag,
"MarqueeView",
{ type: "onStop", isStop: !this.start }
)
buildCustomComponent
创建 RNSurface 加载 JSBundle 时,传入 buildCustomComponent 用于加载原生 Fabric 组件:
import { RNAbility, ComponentBuilderContext, RNSurface } from "rnoh";
import { MarqueeView } from '../customView/MarqueeView'
@Builder
public buildCustomComponent(ctx: ComponentBuilderContext) {
if (ctx.descriptor.type === MarqueeView.NAME) {
MarqueeView({
ctx: ctx.rnohContext,
tag: ctx.descriptor.tag
})
}
}
...
RNSurface({
...
buildCustomComponent: this.buildCustomComponent,
})
3. 编写 Codegen 的 C++ 代码
开发者可以使用Codegen生成C++侧的胶水代码,也可以手动实现这部分代码。在本节中会详细介绍如何手动实现这部分代码。
-
首先创建属性 Props 和 事件 Emitter 两部分的 C++ 类,在 Descriptor 中进行绑定。
-
实现 MarqueeViewEventEmitRequestHandler 的 handleEvent 方法,根据原生消息的事件名,调用 eventEmitter 向 RN 侧组件发送事件消息。
-
实现 MarqueeViewJSIBinder 类的属性和事件绑定方法。
-
实现 MarqueeViewNapiBinder 类的属性映射方法。
-
将以上文件引入到 SampleTurboModulePackage 的对应方法实现中进行绑定。
Props
创建 Props 的 C++ 文件用于定义 MarqueeView 的 Descriptor 对应的属性。Props.h:
#include <jsi/jsi.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/debug/react_native_assert.h>
namespace facebook {
namespace react {
class JSI_EXPORT MarqueeViewProps final : public ViewProps {
public:
MarqueeViewProps() = default;
MarqueeViewProps(const PropsParserContext &context, const MarqueeViewProps &sourceProps, const RawProps &rawProps);
#pragma mark - Props
std::string src{""};
};
} // namespace react
} // namespace facebook
// Props.cpp
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include "Props.h"
namespace facebook {
namespace react {
MarqueeViewProps::MarqueeViewProps(
const PropsParserContext &context,
const MarqueeViewProps &sourceProps,
const RawProps &rawProps): ViewProps(context, sourceProps, rawProps),
src(convertRawProp(context, rawProps, "src", sourceProps.src, {""}))
{}
} // namespace react
} // namespace facebook
MarqueeViewEventEmitter
MarqueeViewEventEmitter.h 中添加 onStop 方法,并自定义了属性结构体:
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class JSI_EXPORT MarqueeViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
struct OnStop {
bool isStop;
};
void onStop(OnStop value) const;
};
} // namespace react
} // namespace facebook
MarqueeViewEventEmitter.cpp 中实现 onStop 事件的发送和参数绑定:
#include "MarqueeViewEventEmitter.h"
namespace facebook {
namespace react {
void MarqueeViewEventEmitter::onStop(OnStop event) const {
dispatchEvent("stop", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "isStop", event.isStop);
return payload;
});
}
} // namespace react
} // namespace facebook
MarqueeViewComponentDescriptor.h
将 MarqueeViewProps, MarqueeViewEventEmitter 绑定到 MarqueeViewComponentDescriptor 中:
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include "MarqueeViewEventEmitter.h"
#include "Props.h"
namespace facebook {
namespace react {
extern const char MarqueeViewComponentName[] = "MarqueeView";
using MarqueeViewShadowNode = ConcreteViewShadowNode<MarqueeViewComponentName, MarqueeViewProps, MarqueeViewEventEmitter>;
using MarqueeViewComponentDescriptor = ConcreteComponentDescriptor<MarqueeViewShadowNode>;
} // namespace react
} // namespace facebook
MarqueeViewEventEmitRequestHandler
handleEvent 方法中根据事件名调用事件消息发送方法 eventEmitter->onStop(event):
class MarqueeViewEventEmitRequestHandler : public EventEmitRequestHandler {
public:
void handleEvent(EventEmitRequestHandler::Context const &ctx) override {
if (ctx.eventName != "MarqueeView") {
return;
}
ArkJS arkJs(ctx.env);
auto eventEmitter = ctx.shadowViewRegistry->getEventEmitter<react::MarqueeViewEventEmitter>(ctx.tag);
if (eventEmitter == nullptr) {
return;
}
MarqueeViewEventType type = getMarqueeViewEventType(arkJs, ctx.payload);
switch (type) {
case MarqueeViewEventType::MARQUEE_VIEW_ON_STOP: {
bool isStop = (bool)arkJs.getBoolean(arkJs.getObjectProperty(ctx.payload, "isStop"));
react::MarqueeViewEventEmitter::OnStop event{isStop};
eventEmitter->onStop(event);
break;
}
default:
break;
}
};
};
MarqueeViewJSIBinder
JSIBinder 是 RN 侧的属性和方法在 JSI 层的实现,主要调用了 object.setProperty(rt, “src”, “string”) 和 events.setProperty(rt, “topStop”, createDirectEvent(rt, “onStop”)) 这两个方法,events.setProperty 中注意 topStop 和 onStop 的命名规则:
#pragma once
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
namespace rnoh {
class MarqueeViewJSIBinder : public ViewComponentJSIBinder {
facebook::jsi::Object createNativeProps(facebook::jsi::Runtime &rt) override {
auto object = ViewComponentJSIBinder::createNativeProps(rt);
object.setProperty(rt, "src", "string");
return object;
}
facebook::jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override {
facebook::jsi::Object events(rt);
events.setProperty(rt, "topStop", createDirectEvent(rt, "onStop"));
return events;
}
};
} // namespace rnoh
NapiBinder
实现 C++ 代码和原生组件代码之间的属性映射,其中 .addProperty(“src”, props->src) 为 MarqueeViewDescriptor 的 props 增加了 src 字段;如果未添加该代码,MarqueeView 就需要从 rawProps 中获取 src:
#include "RNOHCorePackage/ComponentBinders/ViewComponentNapiBinder.h"
#include "Props.h"
namespace rnoh {
class MarqueeViewNapiBinder : public ViewComponentNapiBinder {
public:
napi_value createProps(napi_env env, facebook::react::ShadowView const shadowView) override {
napi_value napiViewProps = ViewComponentNapiBinder::createProps(env, shadowView);
if (auto props = std::dynamic_pointer_cast<const facebook::react::MarqueeViewProps>(shadowView.props)) {
return ArkJS(env)
.getObjectBuilder(napiViewProps)
.addProperty("src", props->src)
.build();
}
return napiViewProps;
};
};
} // namespace rnoh
SampleTurboModulePackage
在 SampleTurboModulePackage.h 中添加自定义组件相关的方法声明:
#include "RNOH/Package.h"
namespace rnoh {
class SampleTurboModulePackage : public Package {
public:
std::vector<facebook::react::ComponentDescriptorProvider> createComponentDescriptorProviders() override;
ComponentNapiBinderByString createComponentNapiBinderByName() override;
ComponentJSIBinderByString createComponentJSIBinderByName() override;
EventEmitRequestHandlers createEventEmitRequestHandlers() override;
};
} // namespace rnoh
使用 MarqueeViewComponentDescriptor、MarqueeViewEventEmitRequestHandler、MarqueeViewNapiBinder、MarqueeViewJSIBinder 在 SampleTurboModulePackage.cpp 中完成对应方法实现:
std::vector<react::ComponentDescriptorProvider> SampleTurboModulePackage::createComponentDescriptorProviders() {
return {
react::concreteComponentDescriptorProvider<react::MarqueeViewComponentDescriptor>(),
};
}
EventEmitRequestHandlers SampleTurboModulePackage::createEventEmitRequestHandlers() {
return {std::make_shared<MarqueeViewEventEmitRequestHandler>()};
}
ComponentNapiBinderByString SampleTurboModulePackage::createComponentNapiBinderByName() {
return {{"MarqueeView", std::make_shared<MarqueeViewNapiBinder>()}};
};
ComponentJSIBinderByString SampleTurboModulePackage::createComponentJSIBinderByName() {
return {{"MarqueeView", std::make_shared<MarqueeViewJSIBinder>()}};
};
4. 优化原生ArkTS组件
之前介绍的ArkTS组件实现中,是通过调用对应的属性设置接口完成属性的设置,这种实现方式存在两个缺点:
-
自定义组件属性过多,影响执行效率:若需要使用系统组件的全量属性方法,则需在封装的自定义组件中注册穷举每个属性值。这样会大大影响每个组件的Build效率
-
不利于后期维护:当自定义组件中的系统组件属性发生变更时,自定义组件也需要同步适配。
为了解决上述缺点,ArkTS为每个系统组件提供了 动态属性设置 的方式,包括attributeModifier属性方法。该方法将组件属性设置分离到系统提供的AttributeModifier接口实现类实例中,通过自定义Class类实现AttributeModifier接口对系统组件属性进行扩展。
export class MarqueeModifier implements AttributeModifier<MarqueeAttribute> {
private constructor() {}
private static instance: MarqueeModifier;
protected descriptor: ViewBaseDescriptor = {} as ViewBaseDescriptor;
//提供单例方法获取MarqueeModifier实例。
public static getInstance(): MarqueeModifier {
if (!MarqueeModifier.instance) {
MarqueeModifier.instance = new MarqueeModifier ();
}
return MarqueeModifier.instance;
}
//提供方法设置该组件的描述信息,后面通过解析该描述信息得到该组件实例需要注册的属性和事件。
setDescriptor(descriptor: ViewBaseDescriptor): MarqueeModifier {
this.descriptor = descriptor;
return MarqueeModifier.instance;
}
//接口方法,ArkUI会调用该方法完成最终的MarqueeAttribute操作。
applyNormalAttribute(instance: MarqueeAttribute): void {
instance.width(this.descriptor.layoutMetrics.frame.size.width);
instance.height(this.descriptor.layoutMetrics.frame.size.height);
instance.position({ y: this.descriptor.layoutMetrics.frame.origin.y, x: this.descriptor.layoutMetrics.frame.origin.x });
if (this.descriptor.props.backgroundColor) {
instance.backgroundColor(this.descriptor.props.backgroundColor);
}
/* ...... 其他需要设置的属性*/
}
}
@Builder
export function marqueeBuilder(ctx: RNOHContext, descriptor: ViewBaseDescriptor) {
Marquee(···) {
//通过AttributeModifier方法动态获取该组件实例化需要注册的属性和事件
.attributeModifier(MarqueeModifier.getInstance().setDescriptor(descriptor))
}
}
如何创建C-API自定义组件
创建一个 Fabric 组件需要实现以下的代码:
-
ComponentInstance:ComponentInstance文件是该自定义组件所有的逻辑集合,也是自定义组件主要需要实现的部分,简单理解就是,一个自定义组件文件对应一个ComponentInstance。
-
Package文件:作用是声明创建该自定义组件的声明,指定创建Instance时会根据Packeage文件内的名称对应,从而生成对应的Instance对象。
-
Props:Props是ComponentInstance的props参数声明,具体作用是JS的参数传递,在自定义组件内部由父组件往子组件发送数据的时候也需要用到props。
-
EventEmitter:EventEmitter是ComponentInstance的事件声明,主要作用是获取前端设置的事件回调,在组件内部以合适的时机触发。
-
ShadowNode:ShadowNode是ComponentInstance创建时所需要声明的其中一个类,将对应的Props,EventEmitter和ComponentName(就是创建ComponentInstance的名字)组合起来。
1. 编写 RN 调用 Fabric 组件的代码
本节以 ButtonView 为例,介绍了 Fabric C-API 自定义组件的实现步骤。
编写ButtonViewNativeComponent.tsx,注意,如果要使用 Codegen ,文件必须以NativeComponent命名。在文件中使用 codegenNativeComponent 创建 ButtonView 组件,其中 ButtonViewProps 里声明了 buttonText 属性和 onButtonClick 事件:
export type OnButtonClickEventData = Readonly<{
isButtonClick: boolean,
type: string,
}>;
export interface ButtonViewProps extends ViewProps {
buttonText: string,
onButtonClick?: DirectEventHandler<OnButtonClickEventData>;
}
export default codegenNativeComponent<ButtonViewProps>(
'CustomButtonView',
) as HostComponent<ButtonViewProps>;
和其他标准组件的创建方式一样,在组件容器内添加 ButtonView 标签:
<ButtonView
buttonText={"ButtonView: " + (buttonClick ? 'Click' : 'No Click')}
ref={nativeRefButton}
style= {{height: 50}}
onButtonClick={(e) => {
setButtonClick(e.nativeEvent.isButtonClick);
}}
/>
2. 编写C-API 原生实现代码
C-API组件结构
原生端自定义组件主要包含两个部分,ButtonViewComponentInstance.cpp 以及 ButtonViewNode.cpp,其中ButtonViewComponentInstance为JS侧ButtonView组件对应的原生端实例,它继承RN框架中的组件实例模板类CppComponentInstance并转换为自己组件实现的ShadowNode类型,它应该重写基类上的一些通用方法,包括onChildInserted插入子节点、getLocalRootArkUINode获取根节点以及onPropsChanged设置组件属性等,也可以实现组件的特有方法。同时它继承ButtonViewNode中声明的代理类ButtonViewNodeDelegate,并重写代理类上的onXXX事件用于上报事件。CppComponentInstance是所有自定义组件ComponentInstance的父类,所有组件都继承于这个类,这个类包含了对组件进行操作的一些基础方法。
// ButtonViewComponentInstance.h
#include "RNOH/CppComponentInstance.h"
#include "ButtonViewNode.h"
#include "ButtonViewComponentDescriptor.h"
...
namespace rnoh {
class ButtonViewComponentInstance : public CppComponentInstance<facebook::react::ButtonViewShadowNode>, public ButtonViewNodeDelegate {
private:
ButtonViewNode m_buttonViewNode;
...
public:
// 重写CppComponentInstance中的通用方法
ButtonViewComponentInstance(Context context);
void onChildInserted(ComponentInstance::Shared const &childComponentInstance, std::size_t index) override;
void onChildRemoved(ComponentInstance::Shared const &childComponentInstance) override;
ButtonViewNode &getLocalRootArkUINode() override;
void onPropsChanged(SharedConcreteProps const &props) override;
// 处理Command命令
void handleCommand(std::string const &commandName, folly::dynamic const &args) override;
...
// 重写ButtonViewNodeDelegate的事件上报方法
void onButtonClick() override;
...
};
} // namespace rnoh
ButtonViewNode对应具体的鸿蒙原生组件节点,它作为成员被组件实例持有,当设置属性或处理指令的时候,ButtonViewComponentInstance会调用ButtonViewNode上实现的具体方法,以ButtonView在RN侧暴露的buttonText属性为例,在JS侧设置或更新属性的时候,框架会调用ButtonViewComponentInstance上的onPropsChanged方法,这个方法中会去调用ButtonViewNode上的setLabel方法去设置资源。
#include "RNOH/arkui/ArkUINode.h"
#include "RNOH/arkui/NativeNodeApi.h"
...
namespace rnoh {
// 代理类的声明
class ButtonViewNodeDelegate {
public:
virtual ~ButtonViewNodeDelegate() = default;
// 组件的事件接口声明
virtual void onButtonClick(){};
};
class ButtonNode : public ArkUINode {
protected:
ButtonNodeDelegate* m_buttonNodeDelegate;
public:
ButtonNode();
~ButtonNode();
// 实现Node的通用方法
void insertChild(ArkUINode& child, std::size_t index);
void removeChild(ArkUINode& child);
void onNodeEvent(ArkUI_NodeEventType eventType, EventArgs& eventArgs) override;
ButtonNode& setButtonNodeDelegate(ButtonNodeDelegate* buttonNodeDelegate);
···
// 实现组件的属性设置方法
ButtonNode& setLabel(const std::string &src);
};
} // namespace rnoh
属性
在 RN 侧设置的属性,会通过props传递到ComponentInstance中。通过ComponentInstance中的onPropsChanged可以获取到变化后的 Props。在onPropsChanged中需要做一次diff判断,然后就可以调用ComponentInstance保存的ComponentNode对象所实现的各种属性设置方法:
// ButtonViewComponentInstance.cpp
void ButtonViewComponentInstance::onPropsChanged(
SharedConcreteProps const& props) {
CppComponentInstance::onPropsChanged(props);
if (!m_props || props->buttonText != m_props->buttonText) {
m_buttonNode.setLabel(props->buttonText);
}
}
在ComponentNode中需要定义并实现对应属性的设置:
// ButtonNode.cpp
ButtonNode& ButtonNode::setLabel(const std::string &src) {
ArkUI_AttributeItem labelItem = {.string = src.c_str()};
maybeThrow(NativeNodeApi::getInstance()->setAttribute(
m_nodeHandle, NODE_BUTTON_LABEL, &labelItem));
return *this;
}
指令
RN 侧需要调用 UIManager.dispatchViewManagerCommand 向原生发送消息:
UIManager.dispatchViewManagerCommand(
findNodeHandle(nativeRef.current),
'changeButtonText',
['changeButtonText'],
)
RN框架内部已经封装好了指令通道,原生端只需在instance层重写实现handleCommand方法,根据接收指令名调用node层实现的对应方法即可,传入的参数以object对象的形式记录在args中:
void ButtonViewComponentInstance::handleCommand(
std::string const& commandName,
folly::dynamic const& args) {
if (commandName == "changeButtonText") {
m_buttonNode.setLabel(args[0].asString());
}
}
事件
RN侧添加 onButtonClick 事件回调监听实现:
<ButtonView
···
onButtonClick={(e) => {
// 原生组件调用了 RN 侧的 ButtonView 的 onButtonClick 方法
setButtonClick(e.nativeEvent.isButtonClick);
}}
/>
原生端在instance层重写事件代理类中的onButtonClick方法,并在该方法中调用ButtonViewEventEmitter.h的对应方法,将事件传到RN侧:
void ButtonViewComponentInstance::onButtonClick() {
facebook::react::ButtonViewEventEmitter::OnButtonClick m_onButtonClick;
m_onButtonClick.isButtonClick = true;
m_onButtonClick.type = "custom";
m_eventEmitter->onButtonClick(m_onButtonClick);
}
事件的接收入口在对应的node类中,node类在构造的时候需要注册组件需要监听的事件,并重写onNodeEvent方法,在该方法中调用instance层实现的具体事件方法:
// 组件监听的事件枚举
static constexpr ArkUI_NodeEventType Button_NODE_EVENT_TYPES[] = {
NODE_ON_CLICK};
···
// 在构造Node的时候注册事件监听
ButtonNode::ButtonNode()
: ArkUINode(NativeNodeApi::getInstance()->createNode(
ArkUI_NodeType::ARKUI_NODE_BUTTON)) {
for (auto eventType : Button_NODE_EVENT_TYPES) {
maybeThrow(NativeNodeApi::getInstance()->registerNodeEvent(
m_nodeHandle, eventType, eventType, this));
}
}
···
// 重写onNodeEvent上报事件
void ButtonNode::onNodeEvent(
ArkUI_NodeEventType eventType, EventArgs& eventArgs) {
if (eventType == ArkUI_NodeEventType::NODE_ON_CLICK) {
if (m_buttonNodeDelegate != nullptr) {
m_buttonNodeDelegate->onButtonClick();
}
}
}
3. 编写 Codegen 的 C++ 代码
由于当前C-API版本的Codegen尚未实现,所以C-API版本的组件需要开发者手动添加在其他平台上由 Codegen 生成的 C++ 代码。
-
首先创建属性 Props 和 事件 Emitter 两部分的 C++ 类,在 Descriptor 中进行绑定并注册Node类型。
-
实现 ButtonViewJSIBinder 类的属性和事件绑定方法。
-
将以上文件引入到 SampleTurboModulePackage 的对应方法实现中进行绑定。
Props
创建 Props 的 C++ 文件用于定义 ButtonView 的属性。Props.h:
#pragma once
#include <jsi/jsi.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/debug/react_native_assert.h>
namespace facebook {
namespace react {
class JSI_EXPORT ButtonViewProps final : public ViewProps {
public:
ButtonViewProps() = default;
ButtonViewProps(const PropsParserContext &context, const ButtonViewProps &sourceProps, const RawProps &rawProps);
#pragma mark - Props
std::string buttonText{""};
};
} // namespace react
} // namespace facebook
// Props.cpp
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include "Props.h"
namespace facebook {
namespace react {
ButtonViewProps::ButtonViewProps(
const PropsParserContext &context,
const ButtonViewProps &sourceProps,
const RawProps &rawProps): ViewProps(context, sourceProps, rawProps),
buttonText(convertRawProp(context, rawProps, "buttonText", sourceProps.buttonText, {""}))
{}
} // namespace react
} // namespace facebook
ButtonViewEventEmitter
ButtonViewEventEmitter.h 中添加 onButtonClick 方法,并自定义了属性结构体:
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class JSI_EXPORT ButtonViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
struct OnButtonClick {
bool isButtonClick;
std::string type;
};
void onButtonClick(OnButtonClick value) const;
};
} // namespace react
} // namespace facebook
ButtonViewEventEmitter.cpp 中实现 onButtonClick 事件的发送和参数绑定:
#include "ButtonViewEventEmitter.h"
namespace facebook {
namespace react {
void ButtonViewEventEmitter::onButtonClick(OnButtonClick event) const {
dispatchEvent("topButtonClick", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "isButtonClick", event.isButtonClick);
payload.setProperty(runtime, "type", event.type);
return payload;
});
}
} // namespace react
} // namespace facebook
ButtonViewComponentDescriptor.h
将 ButtonViewProps, ButtonViewEventEmitter 绑定到 ButtonViewShadowNode中:
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include "ButtonViewEventEmitter.h"
#include "Props.h"
namespace facebook {
namespace react {
const char ButtonViewComponentName[] = "ButtonView";
using ButtonViewShadowNode = ConcreteViewShadowNode<ButtonViewComponentName, ButtonViewProps, ButtonViewEventEmitter>;
using ButtonViewComponentDescriptor = ConcreteComponentDescriptor<ButtonViewShadowNode>;
} // namespace react
} // namespace facebook
ButtonViewJSIBinder
JSIBinder 是 RN 侧的属性和方法在 JSI 层的实现,主要调用了 object.setProperty(rt, “buttonText”, “string”) 和 events.setProperty(rt, “topButtonClick”, createDirectEvent(rt, “onButtonClick”)); 这两个方法,events.setProperty 中注意 topButtonClick 和 onButtonClick 的命名规则:
#pragma once
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
namespace rnoh {
class ButtonViewJSIBinder : public ViewComponentJSIBinder {
facebook::jsi::Object createNativeProps(facebook::jsi::Runtime &rt) override {
auto object = ViewComponentJSIBinder::createNativeProps(rt);
object.setProperty(rt, "buttonText", "string");
return object;
}
facebook::jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override {
facebook::jsi::Object events(rt);
events.setProperty(rt, "topButtonClick", createDirectEvent(rt, "onButtonClick"));
return events;
}
};
} // namespace rnoh
SampleTurboModulePackage
在SampleTurboModulePackage.h中添加自定义组件相关的方法声明:
#include "RNOH/Package.h"
namespace rnoh {
class SampleTurboModulePackage : public Package {
public:
ComponentInstanceFactoryDelegate::Shared createComponentInstanceFactoryDelegate() override;
ComponentJSIBinderByString createComponentJSIBinderByName() override;
// ArkTS版本使用的通用方法
···
};
} // namespace rnoh
创建ButtonViewPackageComponentInstanceFactoryDelegate对象,使用ButtonViewPackageComponentInstanceFactoryDelegate、ButtonViewJSIBinder、ButtonViewComponentInstance 在 SampleTurboModulePackage.cpp 中完成对应方法实现:
class ButtonViewPackageComponentInstanceFactoryDelegate : public ComponentInstanceFactoryDelegate {
public:
using ComponentInstanceFactoryDelegate::ComponentInstanceFactoryDelegate;
ComponentInstance::Shared create(ComponentInstance::Context ctx) override {
if (ctx.componentName == "CustomButtonView") {
return std::make_shared<ButtonViewComponentInstance>(std::move(ctx));
}
return nullptr;
}
};
···
ComponentJSIBinderByString SampleTurboModulePackage::createComponentJSIBinderByName() {
return {
{"MarqueeView", std::make_shared<MarqueeViewJSIBinder>(),},
{"ButtonView", std::make_shared<ButtonViewJSIBinder>()}
};
};
ComponentInstanceFactoryDelegate::Shared SampleTurboModulePackage::createComponentInstanceFactoryDelegate() override {
return std::make_shared<ButtonViewPackageComponentInstanceFactoryDelegate>();
}
···
4. 在自定义组件中使用其他自定义组件
在自定义的组件中,可能并不是由一个基础组件实现的,而是由多个基础组件相互组合实现,例如自定义的Scroll,内部可以持有一个Stack组件。每一个组件的ComponentInstance中,都持有各个组件的node对象,node中提供了修改属性发送事件等各种操作。所以开发者可以在同一个ComponentInstance中记录多个node对象,通过重写getLocalRootArkUINode()方法,用于定义对外暴露的组件节点;重写onChildInserted()方法,用于将子节点插入组件的容器类中;并在ComponentInstance中增加多个node之间相互处理的逻辑,即可完成开发。
总是有很多小伙伴反馈说:鸿蒙开发不知道学习哪些技术?不知道需要重点掌握哪些鸿蒙开发知识点? 为了解决大家这些学习烦恼。在这准备了一份很实用的鸿蒙全栈开发学习路线与学习文档给大家用来跟着学习。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)技术知识点。
《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN/733GH/overview
OpenHarmony 开发环境搭建
《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN/733GH/overview
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
- 系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……