目录结构

AutolinkingSample
├── NativeProject harmony工程
├── react-native-oh RNOH前端及手脚架
├── ReactProject 前端工程
├── screenshots 效果图
├── third-party-library-sample RN三方件示例
└── README.md

环境搭建

  1. third-party-library-sample 中运行 npm pack 进行打包;
  2. ReactProject 目录下执行 npm i @react-native-oh/react-native-harmony@x.x.xyarn add @react-native-oh/react-native-harmony@x.x.x 安装依赖;
  3. 修改 NativeProject/hvigor/hvigor-config.json5@rnoh/hvigor-plugin 的版本号;
  4. 用 DevEco Studio 打开 NativeProject,执行 Sync and Refresh Project
  5. 点击 File > Project Structure > Signing Configs,登录并完成签名;
  6. ReactProject 目录下执行 npm start 启动Metro;
  7. 点击 DevEco Studio 右上角的 run 启动项目;

效果预览

启动后页面效果如下:

在这里插入图片描述

  1. 点击【点击滚到顶】按钮,弹窗会滚到顶部;
  2. 弹窗会滚到顶部后向下拉,会根据滚动位置决定最终停留在中间还是回到顶部;
  3. 点击【选项1/2/3】,选项右侧会显示选中状态;
  4. 点击【确定】按钮,会在VSCode控制台打印对应选项的value,如:{"target": 14, "value": [1]}
  5. 点击【现在是单选】按钮,按钮文案会改成【现在是多选】,继续点击【选项1/2/3】,可以让多个选项切换到选中状态;
  6. 点击【确定】按钮,会在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,实际滚动距离可能小于预期滚动位置通常相对于滚动容器的顶部。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐