ReactNative项目Openharmony三方库集成实战:@react-native-clipboard/clipboard
在移动应用开发中,剪贴板(Clipboard)功能是实现复制粘贴操作的基础能力。无论是复制文本、分享内容还是保存数据,都离不开剪贴板的支持。是 React Native 生态中官方推荐的剪贴板库,提供了跨平台的剪贴板读写能力。库名称版本信息1.13.3: 支持 RN 0.72 版本1.16.3: 支持 RN 0.77 版本官方仓库鸿蒙仓库主要功能📋 复制文本到剪贴板📖 从剪贴板读取文本🔄 监
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
📋 前言
在移动应用开发中,剪贴板(Clipboard)功能是实现复制粘贴操作的基础能力。无论是复制文本、分享内容还是保存数据,都离不开剪贴板的支持。@react-native-clipboard/clipboard 是 React Native 生态中官方推荐的剪贴板库,提供了跨平台的剪贴板读写能力。
🎯 库简介
基本信息
- 库名称:
@react-native-clipboard/clipboard - 版本信息:
1.13.3+@react-native-ohos/clipboard: 支持 RN 0.72 版本1.16.3+@react-native-ohos/clipboard: 支持 RN 0.77 版本
- 官方仓库: https://github.com/react-native-clipboard/clipboard
- 鸿蒙仓库: https://github.com/react-native-oh-library/clipboard
- 主要功能:
- 📋 复制文本到剪贴板
- 📖 从剪贴板读取文本
- 🔄 监听剪贴板变化
- 🌐 跨平台统一 API
为什么需要 @react-native-clipboard/clipboard?
| 特性 | React Native 原生 Clipboard | @react-native-clipboard/clipboard |
|---|---|---|
| 写入文本 | ✅ 支持 | ✅ 支持 |
| 读取文本 | ⚠️ 仅 iOS | ✅ 全平台支持 |
| 监听变化 | ❌ 不支持 | ✅ 支持 |
| 类型安全 | ❌ 不支持 | ✅ TypeScript 支持 |
| 维护状态 | ❌ 已弃用 | ✅ actively maintained |
| HarmonyOS 支持 | ❌ 不支持 | ✅ 完全支持 |
支持的功能
| 功能 | 说明 | HarmonyOS 支持 |
|---|---|---|
| setString | 写入文本 | ✅ |
| getString | 读取文本 | ✅ |
| hasString | 检查是否有文本 | ✅ |
| addListener | 监听剪贴板变化 | ✅ |
| removeAllListeners | 移除所有监听器 | ✅ |
📦 安装步骤
1. 安装依赖

在项目根目录执行以下命令:
# RN 0.72 版本
npm install @react-native-ohos/clipboard@1.13.3-rc.1
# RN 0.77 版本
npm install @react-native-ohos/clipboard@1.16.3
2. 验证安装
安装完成后,检查 package.json 中是否包含:
{
"dependencies": {
"@react-native-ohos/clipboard": "^1.13.3-rc.1"
}
}
🔧 HarmonyOS 平台配置
权限配置
剪贴板操作需要 ohos.permission.READ_PASTEBOARD 权限,该权限等级为 system_basic,授权方式为 user_grant。
在 harmony/entry/src/main/module.json5 中添加权限声明:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.READ_PASTEBOARD",
"reason": "$string:clipboard_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
}
]
}
}
在 harmony/entry/src/main/resources/zh_CN/element/string.json 中添加权限说明:
{
"string": [
{
"name": "clipboard_permission_reason",
"value": "应用需要访问剪贴板以支持复制粘贴功能"
}
]
}
[!IMPORTANT] 由于
READ_PASTEBOARD是 system_basic 级别权限,需要使用 ACL 签名。请参考下方的签名配置步骤。
修改签名配置(解决 9568289 错误)⭐
由于剪贴板读取权限属于 system_basic 级别,需要修改签名模板文件。
步骤 1:修改 Debug 签名模板
找到 SDK 目录下的签名模板文件:
{SDK路径}/openharmony/toolchains/lib/UnsgnedDebugProfileTemplate.json
例如:d:\DevEco Studio\sdk\default\openharmony\toolchains\lib\UnsgnedDebugProfileTemplate.json
打开文件,修改以下内容:
1. 将 APL 等级从 normal 改为 system_basic:
"bundle-info": {
...
"apl": "system_basic",
...
}
2. 在 acls 中添加允许的权限:
"acls": {
"allowed-acls": [
"ohos.permission.READ_PASTEBOARD"
]
}
步骤 2:在 DevEco Studio 中重新签名
- 打开 DevEco Studio
- 点击 File > Project Structure > Project > Signing Configs
- 取消勾选 “Automatically generate signature”
- 重新勾选 “Automatically generate signature”
- 等待自动签名完成
- 点击 OK
步骤 3:重新运行应用
签名完成后,重新运行应用即可正常安装。
⚠️ 注意:修改签名模板后必须重新签名才能生效。如果仍然报错 9568289,请尝试清理项目后重新构建。
原生模块配置(RN 0.72)
RN 0.72 版本需要手动链接原生模块。
1. 引入原生端代码
打开 harmony/entry/oh-package.json5,添加依赖:
"dependencies": {
"@rnoh/react-native-openharmony": "0.72.90",
"@react-native-ohos/clipboard": "file:../../node_modules/@react-native-ohos/clipboard/harmony/clipboard.har"
}
点击右上角的 sync 按钮,或在终端执行:
cd harmony/entry
ohpm install
2. 配置 CMakeLists.txt
打开 harmony/entry/src/main/cpp/CMakeLists.txt,添加:
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/clipboard/src/main/cpp" ./clipboard)
# RNOH_END: manual_package_linking_1
# RNOH_BEGIN: manual_package_linking_2
+ target_link_libraries(rnoh_app PUBLIC rnoh_clipboard)
# RNOH_END: manual_package_linking_2
3. 配置 PackageProvider.cpp
打开 harmony/entry/src/main/cpp/PackageProvider.cpp,添加:
+ #include "ClipboardPackage.h"
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
return {
+ std::make_shared<ClipboardPackage>(ctx),
};
}
4. 配置 RNPackagesFactory.ts
打开 harmony/entry/src/main/ets/RNPackagesFactory.ts,添加:
+ import {ClipboardPackage} from '@react-native-ohos/clipboard/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [
+ new ClipboardPackage(ctx)
];
}
📖 API 详解
基础 API
setString(content: string): void
将文本写入剪贴板。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| content | string | 是 | 要复制的文本 |
import Clipboard from '@react-native-clipboard/clipboard';
// 复制文本到剪贴板
Clipboard.setString('Hello, HarmonyOS!');
getString(): Promise<string>
从剪贴板读取文本。
// 读取剪贴板内容
const text = await Clipboard.getString();
console.log('剪贴板内容:', text);
hasString(): Promise<boolean>
检查剪贴板中是否有文本内容。
// 检查剪贴板是否有内容
const hasContent = await Clipboard.hasString();
if (hasContent) {
const text = await Clipboard.getString();
console.log('剪贴板内容:', text);
}
事件监听(这部分在harmony上并不支持)

addListener(event: string, callback: Function): EmitterSubscription
监听剪贴板变化事件。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| event | string | 是 | 事件名称 |
| callback | Function | 是 | 回调函数 |
import { useEffect } from 'react';
useEffect(() => {
// 监听剪贴板变化
const subscription = Clipboard.addListener(() => {
console.log('剪贴板内容已变化');
});
return () => {
// 移除监听器
subscription.remove();
};
}, []);
removeAllListeners(event: string): void
移除指定事件的所有监听器。
// 移除所有剪贴板变化监听器
Clipboard.removeAllListeners('change');
📱 完整示例

本节展示一个完整的剪贴板工具应用,包含复制、粘贴、历史记录等功能。
import React, { useState, useEffect, useCallback } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
FlatList,
Alert,
SafeAreaView,
} from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
interface ClipboardHistory {
id: string;
text: string;
timestamp: number;
}
const ClipboardDemo = () => {
const [inputText, setInputText] = useState('');
const [clipboardText, setClipboardText] = useState('');
const [history, setHistory] = useState<ClipboardHistory[]>([]);
// 读取当前剪贴板内容
const fetchClipboard = useCallback(async () => {
try {
const hasContent = await Clipboard.hasString();
if (hasContent) {
const text = await Clipboard.getString();
setClipboardText(text);
} else {
setClipboardText('');
}
} catch (error) {
console.error('读取剪贴板失败:', error);
}
}, []);
// 复制文本到剪贴板
const copyToClipboard = useCallback(async () => {
if (!inputText.trim()) {
Alert.alert('提示', '请输入要复制的文本');
return;
}
Clipboard.setString(inputText);
// 添加到历史记录
const newItem: ClipboardHistory = {
id: Date.now().toString(),
text: inputText,
timestamp: Date.now(),
};
setHistory(prev => [newItem, ...prev.slice(0, 9)]);
Alert.alert('成功', '文本已复制到剪贴板');
setInputText('');
// 更新当前剪贴板显示
await fetchClipboard();
}, [inputText, fetchClipboard]);
// 粘贴剪贴板内容到输入框
const pasteToInput = useCallback(async () => {
try {
const hasContent = await Clipboard.hasString();
if (hasContent) {
const text = await Clipboard.getString();
setInputText(text);
} else {
Alert.alert('提示', '剪贴板为空');
}
} catch (error) {
Alert.alert('错误', '无法读取剪贴板内容');
}
}, []);
// 从历史记录复制
const copyFromHistory = useCallback((text: string) => {
Clipboard.setString(text);
Alert.alert('成功', '已复制到剪贴板');
fetchClipboard();
}, [fetchClipboard]);
// 清空历史记录
const clearHistory = useCallback(() => {
Alert.alert(
'确认',
'确定要清空历史记录吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
style: 'destructive',
onPress: () => setHistory([])
},
]
);
}, []);
// 监听剪贴板变化
useEffect(() => {
const subscription = Clipboard.addListener(() => {
console.log('剪贴板内容已变化');
fetchClipboard();
});
// 初始读取
fetchClipboard();
return () => {
subscription.remove();
};
}, [fetchClipboard]);
const renderHistoryItem = ({ item }: { item: ClipboardHistory }) => (
<TouchableOpacity
style={styles.historyItem}
onPress={() => copyFromHistory(item.text)}
>
<Text style={styles.historyText} numberOfLines={2}>
{item.text}
</Text>
<Text style={styles.historyTime}>
{new Date(item.timestamp).toLocaleTimeString()}
</Text>
</TouchableOpacity>
);
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>剪贴板工具</Text>
{/* 输入区域 */}
<View style={styles.inputSection}>
<TextInput
style={styles.input}
placeholder="输入要复制的文本..."
value={inputText}
onChangeText={setInputText}
multiline
numberOfLines={3}
/>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.copyButton]}
onPress={copyToClipboard}
>
<Text style={styles.buttonText}>复制</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.pasteButton]}
onPress={pasteToInput}
>
<Text style={styles.buttonText}>粘贴</Text>
</TouchableOpacity>
</View>
</View>
{/* 当前剪贴板内容 */}
<View style={styles.currentSection}>
<Text style={styles.sectionTitle}>当前剪贴板内容</Text>
<View style={styles.clipboardContent}>
<Text style={styles.clipboardText}>
{clipboardText || '剪贴板为空'}
</Text>
</View>
</View>
{/* 历史记录 */}
<View style={styles.historySection}>
<View style={styles.historyHeader}>
<Text style={styles.sectionTitle}>历史记录</Text>
{history.length > 0 && (
<TouchableOpacity onPress={clearHistory}>
<Text style={styles.clearText}>清空</Text>
</TouchableOpacity>
)}
</View>
<FlatList
data={history}
keyExtractor={(item) => item.id}
renderItem={renderHistoryItem}
ListEmptyComponent={
<Text style={styles.emptyText}>暂无历史记录</Text>
}
/>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
paddingVertical: 16,
backgroundColor: '#fff',
},
inputSection: {
backgroundColor: '#fff',
padding: 16,
marginBottom: 8,
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
minHeight: 80,
textAlignVertical: 'top',
},
buttonRow: {
flexDirection: 'row',
marginTop: 12,
gap: 12,
},
button: {
flex: 1,
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
copyButton: {
backgroundColor: '#00adf5',
},
pasteButton: {
backgroundColor: '#6c757d',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
currentSection: {
backgroundColor: '#fff',
padding: 16,
marginBottom: 8,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
marginBottom: 12,
color: '#333',
},
clipboardContent: {
backgroundColor: '#f8f9fa',
padding: 12,
borderRadius: 8,
minHeight: 60,
},
clipboardText: {
fontSize: 14,
color: '#333',
},
historySection: {
flex: 1,
backgroundColor: '#fff',
padding: 16,
},
historyHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
clearText: {
color: '#ff6b6b',
fontSize: 14,
},
historyItem: {
backgroundColor: '#f8f9fa',
padding: 12,
borderRadius: 8,
marginBottom: 8,
},
historyText: {
fontSize: 14,
color: '#333',
marginBottom: 4,
},
historyTime: {
fontSize: 12,
color: '#999',
},
emptyText: {
textAlign: 'center',
color: '#999',
paddingVertical: 32,
},
});
export default ClipboardDemo;
🔧 高级技巧
1. 封装剪贴板工具类
// utils/clipboard.ts
import Clipboard from '@react-native-clipboard/clipboard';
export class ClipboardUtil {
// 复制并显示提示
static async copy(text: string, showToast = true): Promise<void> {
Clipboard.setString(text);
if (showToast) {
// 使用你项目的 Toast 组件
console.log('已复制到剪贴板');
}
}
// 粘贴并返回内容
static async paste(): Promise<string | null> {
try {
const hasContent = await Clipboard.hasString();
if (hasContent) {
return await Clipboard.getString();
}
return null;
} catch (error) {
console.error('读取剪贴板失败:', error);
return null;
}
}
// 清空剪贴板(通过设置空字符串)
static async clear(): Promise<void> {
Clipboard.setString('');
}
}
2. 自定义 Hook
// hooks/useClipboard.ts
import { useState, useEffect, useCallback } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
export const useClipboard = () => {
const [clipboardText, setClipboardText] = useState('');
const refreshClipboard = useCallback(async () => {
const hasContent = await Clipboard.hasString();
if (hasContent) {
const text = await Clipboard.getString();
setClipboardText(text);
} else {
setClipboardText('');
}
}, []);
const copy = useCallback((text: string) => {
Clipboard.setString(text);
}, []);
useEffect(() => {
const subscription = Clipboard.addListener(refreshClipboard);
refreshClipboard();
return () => subscription.remove();
}, [refreshClipboard]);
return { clipboardText, copy, refresh: refreshClipboard };
};
3. 复制带格式的内容
// 复制 JSON 数据
const copyJSON = (data: object) => {
const jsonString = JSON.stringify(data, null, 2);
Clipboard.setString(jsonString);
};
// 复制 URL
const copyURL = (url: string) => {
Clipboard.setString(url);
};
// 复制多行文本
const copyMultiline = (lines: string[]) => {
Clipboard.setString(lines.join('\n'));
};
❓ 常见问题
1. 无法读取剪贴板内容
问题:调用 getString() 返回空字符串。
解决方案:
- 检查是否已申请
READ_PASTEBOARD权限 - 确认权限已授权(需要在系统设置中手动授权 system_basic 权限)
- 检查剪贴板是否真的有内容
const hasContent = await Clipboard.hasString();
if (!hasContent) {
console.log('剪贴板为空');
return;
}
const text = await Clipboard.getString();
2. 复制后无法粘贴
问题:调用 setString() 后,在其他应用无法粘贴。
解决方案:
- 确认
setString()调用成功 - 检查是否有其他应用占用了剪贴板
- 在 HarmonyOS 上,某些系统应用可能有剪贴板访问限制
3. NativeEventEmitter 警告

问题:导入库时出现警告:new NativeEventEmitter was called with a non-null argument without the required removeListeners method。
原因:HarmonyOS 平台的 clipboard 原生模块没有实现 addListener、removeListeners 等方法,但 JS 库在导入时会自动创建 NativeEventEmitter 实例。
解决方案:
在应用入口文件(如 index.js)中使用 LogBox.ignoreLogs 屏蔽该警告:
import { LogBox } from 'react-native';
// 屏蔽 NativeEventEmitter 相关警告
LogBox.ignoreLogs([
/NativeEventEmitter/,
]);
---
## 📚 参考资料
- [@react-native-clipboard/clipboard 官方文档](https://github.com/react-native-clipboard/clipboard)
- [HarmonyOS 剪贴板开发指南](https://developer.harmonyos.com/)
- [React Native 官方文档](https://reactnative.dev/)
---
## ✅ 总结
`@react-native-clipboard/clipboard` 是一个功能完善、使用简单的剪贴板库,在 HarmonyOS 平台上也能良好运行。通过本文的介绍,你应该能够:
1. ✅ 完成库的安装和配置
2. ✅ 掌握基本的读写操作
3. ✅ 实现剪贴板监听功能
4. ✅ 处理常见问题
虽然 HarmonyOS 平台需要额外的权限配置,但整体使用体验与其他平台保持一致。在实际开发中,建议封装工具类或自定义 Hook 来简化剪贴板操作。
更多推荐


所有评论(0)