React Native 鸿蒙实战:file-selector 文件选择器插件在 HarmonyOS 上的接入与使用
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 鸿蒙应用提供开箱即用的系统文件选择能力。具体包括:
- 弹出系统级文件选择器(调用鸿蒙原生 DocumentViewPicker,非 JS 自绘 UI);
- 文件类型过滤(通过 filter 参数按后缀名筛选,如 .txt、.pdf、.jpg);
- 默认路径设置(通过 path 参数指定初始浏览目录);
- 选择结果回调(onDone 返回选中文件的 URI 路径数组);
- 取消操作回调(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。检查两件事:
- PackageProvider.ets 中是否手动添加了 new RNFileSelectorPackage(ctx);
- 如果用了 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
- 打开适配仓库 Issues 页面,点击"新建 Issue";
- 标题格式:[Bug] 一句话现象,例如 [Bug] FileSelector.Show 调用后崩溃;
- 正文必须包含:复现步骤 / 期望结果 / 实际结果 / 设备与系统版本 / RNOH 版本 / 最小复现代码、日志或截图;
- 提交后跟踪仓库维护者回复,修复发布后关注对应 Tag 更新依赖版本。
5.3 能自己解决:如何提交 PR
- Fork 适配仓库到个人 AtomGit 账号;
- git clone 自己的 fork,基于 master 新建分支:git checkout -b fix/xxx;
- 修改代码(如 ArkTS 侧 TurboModule 实现、JS 侧 index.js)并 commit;
- push 到自己的 fork,在原仓库发起 Pull Request;
- 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 社区入口和三方库资源统一在这里:
更多推荐


所有评论(0)