React Native 鸿蒙实战:file-selector 文件选择器插件在 HarmonyOS 上的接入与使用

库版本:react-native-file-selector 1.0.2-0.0.3(OpenHarmony 适配版)

适配仓库:https://atomgit.com/CPF-RN/rntpc_react-native-file-selector

验证环境:RNOH 0.86.1(对齐 React Native 0.86.3)

设备:鸿蒙 PC(OpenHarmony,2in1 形态)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、环境搭建

React Native 鸿蒙环境搭建请参考官方文档:RNOH 环境搭建指南

本章不重复展开。搭建完成后,确认 pnpm --version 输出 10.x 以上,DevEco Studio 可正常创建鸿蒙工程即可。

二、应用背景

2.1 当前的应用场景与痛点

文档管理、附件上传、图片导入、配置文件加载等场景都需要让用户从设备中选取文件。React Native 应用在 Android 和 iOS 上有成熟的文件选择方案,但鸿蒙系统使用完全不同的文件选择 API(DocumentViewPicker),开发者如果自行实现,需要:

  • 对接鸿蒙 @kit.CoreFileKit 的 DocumentViewPicker,API 与 RN 的 JS 层模型不同;
  • 编写 ArkTS 原生 TurboModule 桥接 JS 调用与系统文件选择器;
  • 处理文件后缀过滤、默认路径、多选回调等参数传递;
  • 配置 codegen spec、HAR 包编译、autolinking 注册等 RNOH 构建流程。

2.2 为什么需要这个库

react-native-file-selector 是 RNOH 社区适配的文件选择器三方库,在 OpenHarmony 平台上基于 ArkTS 重新实现了原生层,直接调用鸿蒙 DocumentViewPicker 弹出系统级文件选择界面,让 React Native 鸿蒙应用无需编写原生代码,即可在 JS 层一行调用完成文件选取。

2.3 解决什么问题

一句话总结:为 React Native 鸿蒙应用提供开箱即用的系统文件选择能力。具体包括:

  1. 弹出系统级文件选择器(调用鸿蒙原生 DocumentViewPicker,非 JS 自绘 UI);
  2. 文件类型过滤(通过 filter 参数按后缀名筛选,如 .txt、.pdf、.jpg);
  3. 默认路径设置(通过 path 参数指定初始浏览目录);
  4. 选择结果回调(onDone 返回选中文件的 URI 路径数组);
  5. 取消操作回调(onCancel 在用户关闭选择器时触发)。

三、功能介绍

功能说明适用场景
选择所有文件FileSelector.Show({ filter: ‘’ }) 不过滤类型通用文件选取、附件上传
按类型筛选FileSelector.Show({ filter: ‘.txt’ }) 仅显示指定后缀文档选择、图片导入
指定默认路径path 参数设置初始目录从特定文件夹开始浏览
多选文件onDone 回调返回路径数组批量文件操作
取消回调onCancel 在用户关闭选择器时触发交互状态恢复
系统原生 UI底层调用 DocumentViewPicker与系统风格一致,无需自绘 UI

四、使用方法

4.1 引入三方库

在 RNOH 工程中接入该库需要完成三个配置:npm 依赖(AtomGit 链接引入)、HAR 包引用、ArkTS Package 注册。

第一步:添加 npm 依赖

在 package.json 的 dependencies 中通过 AtomGit git 依赖方式引入:

{
  "dependencies": {
    "@react-native-oh-tpl/react-native-file-selector": "git+https://atomgit.com/CPF-RN/rntpc_react-native-file-selector.git"
  }
}

执行 pnpm install 拉取依赖。

第二步:添加 HAR 包引用

在 harmony/oh-package.json5 的 dependencies 中添加 HAR 文件引用:

{
  "dependencies": {
    "@react-native-oh-tpl/react-native-file-selector": "file:../node_modules/@react-native-oh-tpl/react-native-file-selector/harmony/file_selector.har"
  }
}

注意 HAR 路径从 oh-package.json5 所在目录算起,到 node_modules 的实际层级。路径写错会导致 ohpm 安装失败,请根据实际工程结构调整。

第三步:注册 ArkTS Package

由于 autolinking 可能不会自动发现该库,需要在 PackageProvider.ets 中手动注册。创建本地 ArkTS 实现文件 entry/src/main/ets/fileselector/FileSelectorPackage.ets:

import {
  RNOHPackage,
  UITurboModuleFactory,
  UITurboModuleContext,
} from '@rnoh/react-native-openharmony';
import { EtsUITurboModule } from '@rnoh/react-native-openharmony/ets';
import { TM } from '@rnoh/react-native-openharmony/generated/ts';
import { picker } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';

interface SelectOptions {
  path?: string;
  filter?: string;
}

class RNFileSelectorModule extends EtsUITurboModule implements TM.RNFileSelector.Spec {
  private context: common.UIAbilityContext = this.ctx.uiAbilityContext;
  private documentPicker = new picker.DocumentViewPicker(this.context);

  Show(
    SelectOptions: SelectOptions,
    onDone?: (res: string[]) => void,
    onCancel?: () => void,
  ): void {
    let documentSelectOptions = new picker.DocumentSelectOptions();
    SelectOptions?.path
      ? (documentSelectOptions.defaultFilePathUri = SelectOptions.path)
      : null;
    SelectOptions?.filter
      ? (documentSelectOptions.fileSuffixFilters = SelectOptions?.filter.split(','))
      : null;

    try {
      this.documentPicker
        .select(documentSelectOptions)
        .then((documentSelectResult: Array<string>) => {
          if (documentSelectResult.length != 0) {
            onDone ? onDone(documentSelectResult) : null;
          } else {
            onCancel ? onCancel() : null;
          }
        });
    } catch (error) {
      console.error('DocumentViewPicker failed: ' + JSON.stringify(error));
    }
  }
}

class FileSelectorUITurboModuleFactory extends UITurboModuleFactory {
  createTurboModule(name: string) {
    if (name === TM.RNFileSelector.NAME) {
      return new RNFileSelectorModule(this.ctx);
    }
    return null;
  }

  hasTurboModule(name: string): boolean {
    return name === TM.RNFileSelector.NAME;
  }
}

export class RNFileSelectorPackage extends RNOHPackage {
  createUITurboModuleFactory(ctx: UITurboModuleContext) {
    return new FileSelectorUITurboModuleFactory(ctx);
  }
}

为什么不用 HAR 自带的 RNFileSelectorPackage?因为 HAR 是基于旧版 react_native_openharmony(5.0.0.490)编译的,当前 RNOH 0.86.1 的 RNOHPackage 接口新增了 createWrappedCustomRNComponentBuilderByComponentNameMap 方法,旧版 HAR 缺少该方法会导致编译报错。在项目内用源码重新编译即可规避此兼容性问题。

然后在 PackageProvider.ets 中注册:

import { RNFileSelectorPackage } from './fileselector/FileSelectorPackage';

export function getRNOHPackages(ctx: RNPackageContext): RNOHPackage[] {
  return [
    ...createRNOHPackagesAutolinking(ctx),
    new RNFileSelectorPackage(ctx),
  ];
}

4.2 核心 API

库只暴露一个方法 FileSelector.Show(),接收单个 props 对象参数:

import FileSelector from '@react-native-oh-tpl/react-native-file-selector';

FileSelector.Show({
  filter: '',        // 文件后缀过滤,空字符串表示不过滤
  path: '',          // 默认打开目录,空字符串表示根目录
  onDone: (paths: string[]) => {
    // 用户选择完成,paths 为选中文件的 URI 数组
    console.log('选中文件:', paths[0]);
  },
  onCancel: () => {
    // 用户取消选择
    console.log('用户取消');
  },
});

重要:onDone 和 onCancel 必须放在 props 对象内部传入,不能作为独立参数传递。FileSelector.Show({ filter, path, onDone, onCancel }) 是唯一正确的调用方式。

按后缀名过滤文件(如只选 .txt 文件):

FileSelector.Show({
  filter: '.txt',
  path: '',
  onDone: (paths: string[]) => {
    console.log('选中的 txt 文件:', paths[0]);
  },
  onCancel: () => {},
});

运行效果:调用 FileSelector.Show() 后弹出鸿蒙系统文件选择窗口,用户可浏览设备文件、按类型筛选,选中后自动关闭并回调 onDone。

4.3 TypeScript 类型声明

如果工程中没有该库的类型定义,需要手动创建 declarations.d.ts:

declare module '@react-native-oh-tpl/react-native-file-selector' {
  import { Component } from 'react';
  import { ViewStyle } from 'react-native';

  interface FileSelectorProps {
    filter?: string;
    path?: string;
    onDone?: (paths: string[]) => void;
    onCancel?: () => void;
    style?: ViewStyle;
  }

  class FileSelector extends Component<FileSelectorProps> {
    static Show(props: FileSelectorProps): void;
  }

  export default FileSelector;
}

4.4 完整示例代码

以下是在 RNOH tester 工程中验证通过的完整示例(FileSelectorExample.tsx),提供两个按钮(选择所有文件 / 仅选择 txt 文件)和选择结果展示:

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import FileSelector from '@react-native-oh-tpl/react-native-file-selector';

export function FileSelectorExample() {
  const [selectedPath, setSelectedPath] = useState<string>('');

  const handleSelectFile = () => {
    FileSelector.Show({
      filter: '',
      path: '',
      onDone: (paths: string[]) => {
        const path = Array.isArray(paths) ? paths[0] : String(paths);
        setSelectedPath(path);
        Alert.alert('选择了文件', path);
      },
      onCancel: () => {
        Alert.alert('提示', '用户取消选择');
      },
    });
  };

  const handleSelectTxt = () => {
    FileSelector.Show({
      filter: '.txt',
      path: '',
      onDone: (paths: string[]) => {
        const path = Array.isArray(paths) ? paths[0] : String(paths);
        setSelectedPath(path);
        Alert.alert('选择了 txt 文件', path);
      },
      onCancel: () => {
        Alert.alert('提示', '用户取消选择');
      },
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>FileSelector Demo</Text>

      <TouchableOpacity style={styles.button} onPress={handleSelectFile}>
        <Text style={styles.buttonText}>选择所有文件</Text>
      </TouchableOpacity>

      <TouchableOpacity
        style={[styles.button, { marginTop: 16 }]}
        onPress={handleSelectTxt}
      >
        <Text style={styles.buttonText}>仅选择 .txt 文件</Text>
      </TouchableOpacity>

      {selectedPath ? (
        <View style={styles.resultBox}>
          <Text style={styles.resultLabel}>已选择:</Text>
          <Text style={styles.resultPath} numberOfLines={3}>
            {selectedPath}
          </Text>
        </View>
      ) : null}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24, backgroundColor: '#fff' },
  title: { fontSize: 22, fontWeight: '600', marginBottom: 24 },
  button: {
    backgroundColor: '#007AFF',
    borderRadius: 8,
    paddingVertical: 14,
    paddingHorizontal: 24,
    alignItems: 'center',
  },
  buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
  resultBox: { marginTop: 24, padding: 16, backgroundColor: '#F2F2F7', borderRadius: 8 },
  resultLabel: { fontSize: 14, color: '#666', marginBottom: 4 },
  resultPath: { fontSize: 14, color: '#333', fontWeight: '500' },
});

运行效果:页面显示两个蓝色按钮,点击"选择所有文件"弹出系统文件选择窗口,不过滤文件类型;点击"仅选择 .txt 文件"则只显示 .txt 后缀的文件。选中文件后页面底部显示文件路径,同时弹出 Alert 提示。

五、FAQ

5.1 常见问题

Q1:编译报 Couldn’t find Turbo Module on the ArkTs side, name: ‘RNFileSelector’

ArkTS 侧没有注册 RNFileSelectorPackage。检查两件事:

  1. PackageProvider.ets 中是否手动添加了 new RNFileSelectorPackage(ctx);
  2. 如果用了 HAR 自带的 RNFileSelectorPackage,可能因 HAR 基于旧版框架编译导致类型不兼容(缺少 createWrappedCustomRNComponentBuilderByComponentNameMap 方法)。改用本文 4.1 节的项目内源码方案即可解决。

Q2:TypeScript 报 无法找到模块的声明文件

该库没有自带 .d.ts 类型声明。在工程根目录创建 declarations.d.ts,添加 declare module 声明(见 4.3 节)。

Q3:FileSelector.Show 报 应有 1 个参数,但获得 3 个

Show() 方法接收单个 props 对象,不是三个独立参数。正确写法:

// ✅ 正确
FileSelector.Show({ filter: '', path: '', onDone: callback, onCancel: callback });

// ❌ 错误
FileSelector.Show({ filter: '', path: '' }, onDone, onCancel);

Q4:HAR 安装失败(ohpm Sync 报错)

检查 oh-package.json5 中 HAR 路径是否正确。路径从 harmony/ 目录算起,到根 node_modules 需要回退到正确层级:

"@react-native-oh-tpl/react-native-file-selector": "file:../node_modules/@react-native-oh-tpl/react-native-file-selector/harmony/file_selector.har"

路径层级写错会导致 ohpm 找不到 HAR 文件。

Q5:真机安装失败(HAP 安装报错)

用 DevEco Studio 打开工程,进入 File > Project Structure > Signing Configs,勾选 Automatically generate signature 后重新运行。

5.2 库本身存在问题:如何提交 Issue

  1. 打开适配仓库 Issues 页面,点击"新建 Issue";
  2. 标题格式:[Bug] 一句话现象,例如 [Bug] FileSelector.Show 调用后崩溃;
  3. 正文必须包含:复现步骤 / 期望结果 / 实际结果 / 设备与系统版本 / RNOH 版本 / 最小复现代码、日志或截图;
  4. 提交后跟踪仓库维护者回复,修复发布后关注对应 Tag 更新依赖版本。

5.3 能自己解决:如何提交 PR

  1. Fork 适配仓库到个人 AtomGit 账号;
  2. git clone 自己的 fork,基于 master 新建分支:git checkout -b fix/xxx;
  3. 修改代码(如 ArkTS 侧 TurboModule 实现、JS 侧 index.js)并 commit;
  4. push 到自己的 fork,在原仓库发起 Pull Request;
  5. PR 描述写清:问题背景 / 修改点 / 鸿蒙真机验证结果(附运行截图),等待维护者评审合入。

六、其他内容

6.1 总结

react-native-file-selector 以极低的接入成本,为 React Native 鸿蒙应用补齐了系统文件选择能力。整个库只有一个 FileSelector.Show() 方法,底层调用鸿蒙 DocumentViewPicker 弹出原生文件选择界面,支持按后缀名过滤、指定默认目录、多选回调等核心功能。接入时注意三点:oh-package.json5 中 HAR 路径层级要正确、PackageProvider.ets 中需手动注册 Package(autolinking 可能不自动发现)、HAR 自带的 ArkTS 代码可能因框架版本不兼容而编译报错,此时用项目内源码替代即可。建议生产环境锁定依赖版本,遇到问题优先查看适配仓库 Issues。

6.2 参考链接

RNOH 社区入口和三方库资源统一在这里:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐