跨平台React Native鸿蒙开发案例一:如何点击页面位置滚动顶部位置
摘要 该项目展示了React Native与OpenHarmony的集成方案,包含完整的工程结构和开发流程。项目采用混合架构,包含Native工程、React前端工程和第三方库示例。环境搭建涉及npm打包、依赖安装、签名配置等步骤。核心功能包括: 滚动交互:实现弹窗滚动控制 选项选择:支持单选/多选模式切换 原生模块调用:演示TurboModule通信 错误处理:包含调试模式下的错误弹窗 关键代码
目录结构
AutolinkingSample
├── NativeProject harmony工程
├── react-native-oh RNOH前端及手脚架
├── ReactProject 前端工程
├── screenshots 效果图
├── third-party-library-sample RN三方件示例
└── README.md
环境搭建
- 在
third-party-library-sample中运行 npm pack 进行打包; - 在
ReactProject目录下执行 npm i @react-native-oh/react-native-harmony@x.x.x或yarn add @react-native-oh/react-native-harmony@x.x.x 安装依赖; - 修改
NativeProject/hvigor/hvigor-config.json5中@rnoh/hvigor-plugin的版本号; - 用 DevEco Studio 打开
NativeProject,执行 Sync and Refresh Project; - 点击 File > Project Structure > Signing Configs,登录并完成签名;
- 在
ReactProject目录下执行 npm start 启动Metro; - 点击 DevEco Studio 右上角的 run 启动项目;
效果预览
启动后页面效果如下:

- 点击【点击滚到顶】按钮,弹窗会滚到顶部;
- 弹窗会滚到顶部后向下拉,会根据滚动位置决定最终停留在中间还是回到顶部;
- 点击【选项1/2/3】,选项右侧会显示选中状态;
- 点击【确定】按钮,会在VSCode控制台打印对应选项的value,如:
{"target": 14, "value": [1]}; - 点击【现在是单选】按钮,按钮文案会改成【现在是多选】,继续点击【选项1/2/3】,可以让多个选项切换到选中状态;
- 点击【确定】按钮,会在VSCode控制台打印对应选项的value,如:
{"target": 14, "value": [1,2]};
import {
AnyJSBundleProvider,
ComponentBuilderContext,
FileJSBundleProvider,
MetroJSBundleProvider,
ResourceJSBundleProvider,
RNApp,
RNOHErrorDialog,
RNOHLogger,
TraceJSBundleProviderDecorator,
RNOHCoreContext
} from '@rnoh/react-native-openharmony';
import { getRNOHPackages } from '../PackageProvider';
@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {}
const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent);
@Entry
@Component
struct Index {
@StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined;
@State shouldShow: boolean = false;
private logger!: RNOHLogger;
aboutToAppear() {
this.logger = this.rnohCoreContext!.logger.clone("Index");
const stopTracing = this.logger.clone("aboutToAppear").startTracing();
this.shouldShow = true;
stopTracing();
}
onBackPress(): boolean | undefined {
// NOTE: this is required since `Ability`'s `onBackPressed` function always
// terminates or puts the app in the background, but we want Ark to ignore it completely
// when handled by RN
this.rnohCoreContext!.dispatchBackPress();
return true;
}
build() {
Column() {
if (this.rnohCoreContext && this.shouldShow) {
if (this.rnohCoreContext?.isDebugModeEnabled) {
RNOHErrorDialog({ ctx: this.rnohCoreContext });
}
RNApp({
rnInstanceConfig: {
createRNPackages: getRNOHPackages,
enableNDKTextMeasuring: true,
enableCAPIArchitecture: true,
arkTsComponentNames: []
},
initialProps: { "foo": "bar" } as Record<string, string>,
appKey: "app_name",
wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder,
jsBundleProvider: new TraceJSBundleProviderDecorator(
new AnyJSBundleProvider([
new MetroJSBundleProvider(),
// NOTE: to load the bundle from file, place it in
// `/data/app/el2/100/base/com.rnoh.tester/files/bundle.harmony.js`
// on your device. The path mismatch is due to app sandboxing on OpenHarmony
new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
]),
this.rnohCoreContext.logger),
})
}
}
.height('100%')
.width('100%')
}
}
import React, { useEffect, useRef, useState } from 'react';
import { StyleSheet, Text, View, Pressable, UIManager, findNodeHandle, Dimensions } from 'react-native';
import { QDGestureFloat, SampleAnyThreadTurboModule, SampleTurboModule } from 'third-party-library';
import SelectBoxApp from './src/SelectBoxApp'
const ScreenHeight = Dimensions.get('window').height;
const App = () => {
const floatRef = useRef(null)
const [addResult, setAddResult] = useState(0)
const [powerResult, setPowerResult] = useState(0)
useEffect(() => {
const result1 = SampleTurboModule.add(2, 4);
setAddResult(result1);
SampleAnyThreadTurboModule.power(2, 4).then(result2 => {
setPowerResult(result2);
});
}, [])
return (
<View style={styles.container}>
<QDGestureFloat
ref={floatRef}
style={styles.container}
stopPercent={0.5}
stopPercentMax={0.75}
onScroll={(event) => {
console.log(event.nativeEvent.offsetY)
}}
>
<View style={{ width: '100%', height: 1000, backgroundColor: 'yellow' }}>
<Pressable onPress={() => {
if (floatRef.current) {
// RN向原生发送消息
UIManager.dispatchViewManagerCommand(
findNodeHandle(floatRef.current),
'scrollTo',
[ScreenHeight, true]
);
}
}}>
<Text style={{ fontSize: 50, color: 'red' }}>点击滚到顶</Text>
</Pressable>
<Text style={{ backgroundColor: 'white' }}>2 + 4 = {addResult}, 2 ^ 4 = {powerResult}</Text>
<SelectBoxApp />
</View>
</QDGestureFloat>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
title: {
backgroundColor: 'green',
color: 'white',
fontSize: 20,
padding: 8,
textAlign: 'center'
}
});
export default App;
这段代码实现了一个React Native中的可点击按钮,其核心功能是:当用户点击这个按钮时,如果存在对应的原生组件引用,则通过React Native的桥接机制向原生端发送命令,让一个可滚动的视图滚动到屏幕底部位置。
详细技术原理分析
1. 组件结构分析
Pressable组件
Pressable是React Native 0.63版本引入的现代化触摸处理组件,用于替代早期的Touchable系列组件。它具有以下特点:
-
更精细的触摸状态管理:支持onPressIn、onPressOut、onLongPress等多种状态
-
更好的性能优化:支持预加载和延迟渲染
-
更丰富的视觉反馈:可以通过style函数根据按压状态动态改变样式
-
跨平台一致性:在iOS和Android上提供统一的交互体验
Text组件,作为Pressable的子组件,用于显示按钮文本。其样式设置: -
fontSize: 50 - 设置较大的字体尺寸,确保按钮足够醒目
-
color: ‘red’ - 使用红色强调这是一个重要的操作按钮
2. 引用系统(Ref System)
floatRef.current的作用
if (floatRef.current) {}
这行代码检查floatRef.current是否存在,这是React引用机制的核心部分:
引用创建方式:
// 方式1:使用useRef Hook(函数组件)
const floatRef = useRef(null);
// 方式2:使用createRef(类组件)
this.floatRef = createRef(null);
引用赋值:
// 在组件渲染时赋值
<SomeScrollView ref={floatRef} />
引用生命周期:
- 挂载阶段:组件挂载后,ref.current指向对应的DOM节点或组件实例
- 更新阶段:组件更新时,ref.current保持最新的引用
- 卸载阶段:组件卸载时,ref.current自动设置为null
- 这种设计避免了在组件未挂载或已卸载时执行操作,防止内存泄漏和运行时错误。
3. React Native桥接机制深度解析
UIManager.dispatchViewManagerCommand方法,这是React Native通信系统的核心API,实现了JavaScript与原生代码的跨语言调用:
方法签名:
UIManager.dispatchViewManagerCommand(
reactTag: number, // 原生视图的标识符
commandID: string|number, // 要执行的命令标识
commandArgs: Array<any> // 命令参数数组
)
底层通信流程:
JavaScript层:调用dispatchViewManagerCommand
C++桥接层:通过JSI(JavaScript Interface)或旧的Bridge模块序列化参数
原生队列:将命令放入原生模块的执行队列
原生执行层:解析命令并调用对应的原生方法
UI更新:在原生线程中执行UI操作,确保线程安全
线程安全机制:
JavaScript代码运行在JS线程
UI操作必须在主线程执行
桥接机制自动处理线程间通信和同步
4. 节点查找机制
findNodeHandle函数原理
const reactTag = findNodeHandle(floatRef.current);
作用:将React组件引用转换为原生视图的唯一标识符(reactTag)
如果目标组件的可滚动内容高度小于ScreenHeight,实际滚动距离可能小于预期滚动位置通常相对于滚动容器的顶部。
更多推荐



所有评论(0)