在 React 中复制到剪贴板的正确方式 (2024)
发布: (2025年12月19日 GMT+8 11:46)
5 min read
原文: Dev.to
Source: Dev.to

复制到剪贴板看起来很简单,对吧?点击一个按钮,文本就会进入剪贴板。完成。
但是如果你曾经尝试在 React 中实现它,你会知道并不是那么直截了当。浏览器 API 已经演变,需要旧版回退,而且正确处理状态也需要思考。
我构建了 @samithahansaka/clipboard 来一次性解决这个问题。以下是原因以及你如何使用它。
现有解决方案的问题
我评估了流行的选项:
| Library / Approach | Weekly Downloads | Main Drawbacks |
|---|---|---|
react-copy-to-clipboard | 500K+ | • 没有 hooks API(需要类组件包装器) • 不支持富(HTML)内容 • 最近一次有意义的更新是多年以前 |
| 手动实现(copy‑paste from Stack Overflow) | – | • 未正确处理回退方案 • 没有 “已复制!” 的状态管理 • 在 Next.js、Remix 等框架下存在 SSR 问题 |
use-clipboard-copy | – | • 不支持 HTML 内容 • 配置选项有限 |
结论: 我需要一些 现代、轻量级 且 完整 的方案。
介绍 @samithahansaka/clipboard
npm install @samithahansaka/clipboard
有何不同
| 特性 | 此库 | 其他 |
|---|---|---|
| Hooks API | ✅ | 有时 |
| Component API | ✅ | 有时 |
| HTML/Rich content | ✅ | ❌ |
| TypeScript | ✅ 一等 | 部分 |
| 打包体积 | ~1 KB | 2‑5 KB |
| 依赖 | 0 | 各不相同 |
| SSR 安全 | ✅ | 经常出错 |
基本用法
import { useCopyToClipboard } from '@samithahansaka/clipboard';
function CopyButton() {
const { copy, copied } = useCopyToClipboard();
return (
<button onClick={() => copy('Hello, World!')}>
{copied ? '✓ Copied!' : 'Copy'}
</button>
);
}
就是这样。copied 状态会在 2 秒后自动重置。
实际示例:代码块组件
import { useCopyToClipboard } from '@samithahansaka/clipboard';
function CodeBlock({ code, language }: { code: string; language: string }) {
const { copy, copied, error } = useCopyToClipboard({
successDuration: 2000,
});
return (
<div className="code-block">
<div className="header">
<span>{language}</span>
<button
onClick={() => copy(code)}
className={copied ? 'success' : ''}
>
{copied ? '✓ Copied!' : 'Copy'}
</button>
</div>
<pre>{code}</pre>
{error && <div className="error">Copy failed</div>}
</div>
);
}
/* Additional snippet example */
{/* {error ? 'Failed!' : copied ? 'Copied!' : 'Copy'} */}
该组件展示了一个代码片段,并配有复制按钮,点击后会在 2 秒 内显示成功状态,同时能够优雅地处理复制错误。
复制富 HTML 内容
这是大多数库的短板所在。需要复制格式化文本,使其在 Google 文档或电子邮件中能够正确粘贴吗?
const { copy } = useCopyToClipboard();
// 同时复制纯文本 **AND** HTML
copy({
text: 'Hello, World!', // 纯文本应用的回退方案
html: '**Hello, World!**' // 兼容应用的富内容
});
粘贴到:
- 记事本 / 终端:
Hello, World! - Google 文档 / 邮件: Hello, World!(加粗)
与 Toast 库集成
无缝兼容任何通知库:
import { useCopyToClipboard } from '@samithahansaka/clipboard';
import { toast } from 'sonner'; // or react-hot-toast, etc.
function ShareButton({ url }: { url: string }) {
const { copy } = useCopyToClipboard({
onSuccess: () => toast.success('Link copied!'),
onError: () => toast.error('Failed to copy'),
});
return (
<button onClick={() => copy(url)}>
Share
</button>
);
}
简单情况的组件 API
更喜欢渲染属性吗?还有组件 API:
import { CopyButton } from '@samithahansaka/clipboard';
function App() {
return (
<CopyButton>
{/* children can be a custom button or any element */}
Copy
</CopyButton>
);
}
<CopyButton>
{({ copy, copied }) => (
<button onClick={copy}>
{copied ? 'Done!' : 'Copy'}
</button>
)}
</CopyButton>
工作原理概述
- 现代浏览器 (Chrome 76+, Firefox 63+, Safari 13.1+):
使用navigator.clipboard.write()实现完整的 HTML 支持。 - 较旧的现代浏览器:
回退到navigator.clipboard.writeText()进行纯文本复制。 - 传统浏览器 (IE 11, 旧版 Safari):
回退到使用隐藏的<textarea>并调用document.execCommand('copy')。
您无需考虑这些细节,它会自动工作。
TypeScript 支持
完整的类型定义已包含:
import {
useCopyToClipboard,
CopyButton,
type CopyContent,
type ClipboardItem,
} from '@samithahansaka/clipboard';
// TypeScript knows exactly what `copy()` accepts
const { copy } = useCopyToClipboard();
copy('string'); // ✅
copy({ text: 'hi' }); // ✅
copy({ text: 'hi', html: '**hi**' }); // ✅
copy(123); // ❌ Type error
SSR‑安全
开箱即用,兼容 Next.js、Remix、Gatsby 或任何 SSR 框架:
- 没有 “window is not defined” 错误。
- 不需要动态导入。
- 直接正常使用即可。
import { useCopyToClipboard } from '@samithahansaka/clipboard';
包大小
整个库的压缩后大小约为 ~1 KB。作比较:
| 库 | 大小(gzip后) |
|---|---|
@samithahansaka/clipboard | ~1 KB |
react-copy-to-clipboard | ~2 KB |
use-clipboard-copy | ~3 KB |
