欢迎加入开源鸿蒙跨平台社区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 中重新签名
  1. 打开 DevEco Studio
  2. 点击 File > Project Structure > Project > Signing Configs
  3. 取消勾选 “Automatically generate signature”
  4. 重新勾选 “Automatically generate signature”
  5. 等待自动签名完成
  6. 点击 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 原生模块没有实现 addListenerremoveListeners 等方法,但 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 来简化剪贴板操作。
Logo

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

更多推荐