- Overview: End-to-End Flow
-
Code Walkthrough
- RN Side: Declaring the Fabric Component
- Using the Component in RN
- Native HarmonyOS Side: ETS Implementation
- Native C++ Glue: Property and Event Binding for Fabric
- Native Event Emission Back to RN
- RN -> Native: Controlling Native State via Commands
-
Deep Dive: The Fabric Architecture
- What is Fabric?
- How Does Fabric Work?
- Benefits vs the Old Bridge
- How to Write Your Own Fabric Component
- Summary
1. Overview: End-to-End Flow
Fabric is React Native's new rendering system, designed for performance and reliability. When integrating a custom native component like a "marquee" (text that scrolls horizontally) into a HarmonyOS app, Fabric allows React Native JS code to seamlessly declare, configure, and control the native view, while also receiving native events.
The flow:
-
JS/TS side: Declare the component's props and events using
codegenNativeComponent(Fabric). - Use in RN: Import and use the component in a React Native screen, passing props and handling events.
- C++/Native bridge: Properties and events are mapped automatically to native code via Fabric's codegen/binder logic.
- HarmonyOS (ETS): The component is implemented as a HarmonyOS custom UI, which responds to JS props, emits events, and can be controlled via commands.
- Event emission: Native can fire events, which are received in JS as callbacks.
- Command/control: JS can send commands to control native component state at runtime (e.g., pause/resume marquee).
2. Code Walkthrough
2.1 RN Side: Declaring the Fabric Component
```typescript name=MarqueeView.tsx
import {
ViewProps,
HostComponent,
} from 'react-native';
import type { DirectEventHandler } from "react-native/Libraries/Types/CodegenTypes";
import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent";
export type OnStopEventData = Readonly<{
isStop: boolean,
type: string,
}>;
export interface MarqueeViewProps extends ViewProps {
src: string, // The text to scroll
onStop?: DirectEventHandler; // Event from native
}
// Fabric codegen: registers 'MarqueeView' with property/event types
export default codegenNativeComponent(
'MarqueeView',
) as HostComponent;
**Key Points:**
- `codegenNativeComponent` is critical: it tells React Native's codegen to generate all the glue for a Fabric component.
- `src` is a prop passed from JS to native.
- `onStop` is an event: native can call this to notify JS (e.g., when the marquee stops).
---
### 2.2 RN Usage: Using the Native Component in JS
```typescript name=GoodsMainPage.tsx (excerpt)
import MarqueeView from '../basic/MarqueeView';
function AppGoods() {
const [marqueeStopped, setMarqueeStopped] = useState(false);
const nativeRef = useRef<any>(null);
// Listen for native events (DeviceEventEmitter also used for custom events)
DeviceEventEmitter.addListener('clickMarqueeEvent', e => {
// ... handle custom event from native
});
return (
<ScrollView>
<View style={styles.container}>
<MarqueeView
src="Super Sale! Consumption is a key part of the social reproduction process..."
ref={nativeRef}
style={{ height: 180, width: '100%', backgroundColor: '#1980E6' }}
onStop={e => {
setMarqueeStopped(e.nativeEvent.isStop);
}}
/>
<GoodsButton
buttonText={"Pause/Resume Marquee: " + (marqueeStopped ? "Stopped" : "Running")}
onPress={() => {
// Send a command to native, using Fabric bridge
UIManager.dispatchViewManagerCommand(
findNodeHandle(nativeRef.current),
'toggleMarqueeState',
[]
);
}}
/>
</View>
</ScrollView>
);
}
Key Points:
- Component used just like any RN component.
- Props (
src) go down to native. - Event (
onStop) is handled as a callback. - Native methods can be triggered via
UIManager.dispatchViewManagerCommand.
2.3 Native HarmonyOS Side: ETS Implementation
```typescript name=MarqueeView.ets
import { Descriptor, ViewBaseProps } from '@rnoh/react-native-openharmony';
import { RNOHContext, RNViewBase } from '@rnoh/react-native-openharmony';
@Component
export struct MarqueeView {
static NAME: string = "MarqueeView"; // Matches JS codegen
ctx!: RNOHContext;
tag: number = 0;
@State private descriptor: Descriptor<"MarqueeView", MarqueeViewProps> = {} as any;
@State start: boolean = false;
@State src: string = "Default text";
aboutToAppear() {
this.start = true;
// Listen for prop changes from RN
this.descriptor = this.ctx.descriptorRegistry.getDescriptor(this.tag);
this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, (newDescriptor) => {
this.descriptor = newDescriptor as MarqueeViewDescriptor;
this.src = (this.descriptor.rawProps as MarqueeViewProps).src;
});
// Listen for RN commands (e.g., pause/resume)
this.ctx.componentCommandReceiver.registerCommandCallback(this.tag, (commandName) => {
if (commandName === "toggleMarqueeState") {
this.start = !this.start; // Toggle running state
// Fire event back to RN
this.ctx.rnInstance.emitComponentEvent(
this.descriptor.tag,
"onStop",
{ isStop: !this.start, type: "custom" }
)
}
});
}
aboutToDisappear() {
this.start = false;
// Cleanup listeners...
}
build() {
RNViewBase({ ctx: this.ctx, tag: this.tag }) {
Column() {
Marquee({
start: this.start,
src: this.src
})
.onTouch(() => {
// Emit a custom event to RN JS
this.ctx.rnInstance.emitDeviceEvent("clickMarqueeEvent", { params: { age: 18 } });
})
}
.height(180)
.justifyContent(FlexAlign.Center)
}
}
}
**Key Points:**
- The `MarqueeView` component receives props (`src`) from RN and reacts to command invocations (e.g., pause/resume).
- It can emit events (`onStop`) back to RN.
- Custom device events (via `emitDeviceEvent`) can be sent as well.
---
### 2.4 Native C++ Glue: Property and Event Binding for Fabric
#### Property Mapping
```cpp name=MarqueeViewJSIBinder.h
facebook::jsi::Object createNativeProps(facebook::jsi::Runtime &rt) override {
auto object = ViewComponentJSIBinder::createNativeProps(rt);
object.setProperty(rt, "src", "string"); // Expose 'src' to JS
return object;
}
Event Mapping
```cpp name=MarqueeViewJSIBinder.h
facebook::jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override {
facebook::jsi::Object events(rt);
events.setProperty(rt, "topStop", createDirectEvent(rt, "onStop")); // Map native event to JS callback
return events;
}
**Explanation:**
- `createNativeProps` tells Fabric which JS props are supported and their types.
- `createDirectEventTypes` registers which events the native component can emit, and how they're mapped back to JS.
---
### 2.5 Native Event Emission Back to RN
#### Native Event Emitter
```cpp name=MarqueeViewEventEmitter.h
class MarqueeViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
struct OnStop {
bool isStop;
std::string type;
};
void onStop(OnStop value) const;
};
```cpp name=MarqueeViewEventEmitter.cpp
void MarqueeViewEventEmitter::onStop(OnStop event) const {
dispatchEvent("stop", event = std::move(event) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "isStop", event.isStop);
payload.setProperty(runtime, "type", event.type);
return payload;
});
}
**Explanation:**
- When the HarmonyOS component wants to notify RN (e.g., that the marquee has stopped), it calls `onStop`.
- This dispatches a JS event named `"stop"` with a payload containing `isStop` and `type`.
- The JS side receives this as the `onStop` callback.
#### Event Dispatching Handler
```cpp name=MarqueeViewEventEmitRequestHandler.h
void handleEvent(EventEmitRequestHandler::Context const &ctx) override {
ArkJS arkJs(ctx.env);
auto eventEmitter = ctx.shadowViewRegistry->getEventEmitter<react::MarqueeViewEventEmitter>(ctx.tag);
if (!eventEmitter) return;
if (ctx.eventName == "onStop") {
bool isStop = ...;
std::string type = ...;
react::MarqueeViewEventEmitter::OnStop event = {isStop, type};
eventEmitter->onStop(event);
}
}
2.6 RN -> Native: Controlling Native State via Commands
```typescript name=GoodsMainPage.tsx (excerpt)
buttonText={"Pause/Resume Marquee"}
onPress={() => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(nativeRef.current),
'toggleMarqueeState', // Command name matches native implementation
[],
);
}}
/>
- The JS side can trigger imperative actions on the native component by sending commands.
- The native component listens for these commands (see `registerCommandCallback`) and updates its state accordingly.
---
## 3. Deep Dive: The Fabric Architecture
### 3.1 What is Fabric?
Fabric is React Native’s new rendering system that:
- Makes UI updates synchronous and more predictable.
- Uses a structure that mirrors the JS and native view trees (Shadow Tree).
- Leverages codegen for type-safe, fast prop/event bridging between JS and native.
### 3.2 How Does Fabric Work?
- **Component Registration**: In JS, you use `codegenNativeComponent` to declare your native view and its props/events.
- **Codegen**: React Native’s codegen tool scans these declarations and generates the C++/Java/ObjC glue code.
- **Property/Event Synchronization**: Updates to props/events are fast, type-safe, and avoid the old "bridge" serialization step.
- **Direct Command/Events**: Fabric supports direct commands and event emission between JS and native, reducing latency.
### 3.3 Benefits vs the Old Bridge
| Old Bridge | Fabric |
| ------------------- | ------------------------- |
| Asynchronous | Synchronous |
| JSON Serialization | Direct struct mapping |
| Manual glue code | Codegen-driven |
| Potential for drift | Type-safe, always in sync |
| Slower, less robust | Faster, more reliable |
### 3.4 How to Write Your Own Fabric Component
1. **JS/TS: Declare with `codegenNativeComponent`**
- Define all props and events with proper types.
2. **Implement Native Component**
- For HarmonyOS, write the ETS component.
- For iOS/Android, implement their native UI logic.
3. **Binder/Glue (C++)**
- Implement property and event mapping, usually with help from codegen.
4. **Use in JS**
- Import and use your component like any other React Native component.
---
## 4. Summary
- **Fabric** enables seamless, high-performance integration of native components in React Native apps.
- The marquee example illustrates: property passing, event handling, and command-based control between RN and HarmonyOS via Fabric.
- **Best practices**: Always use codegen for new native components, keep prop/event naming consistent, and leverage Fabric's glue for robust, maintainable cross-platform UI code.
---
> **References**
>
> - [React Native New Architecture Guide](https://reactnative.dev/docs/architecture-overview)
> - [HarmonyOS JS/ETS Documentation](https://developer.harmonyos.com/en/docs/documentation/js-ets-overview)
Top comments (0)