React에서 클립보드에 복사하는 올바른 방법 (2024)
Source: Dev.to

클립보드에 복사하는 것은 간단해 보이죠? 버튼을 클릭하면 텍스트가 클립보드에 들어갑니다. 끝.
하지만 React에서 이를 구현해 본 적이 있다면 그리 간단하지 않다는 것을 알게 될 겁니다. 브라우저 API가 발전했고, 레거시 폴백이 필요하며, 상태를 올바르게 관리하려면 고민이 필요합니다.
저는 이 문제를 한 번에 해결하기 위해 @samithahansaka/clipboard 를 만들었습니다. 이유와 사용 방법을 소개합니다.
기존 솔루션의 문제점
나는 인기 있는 옵션들을 평가했다:
| 라이브러리 / 접근 방식 | 주간 다운로드 수 | 주요 단점 |
|---|---|---|
react-copy-to-clipboard | 500K+ | • 훅 API가 없음 (클래스‑컴포넌트 래퍼가 필요함) • 풍부한 (HTML) 콘텐츠 지원이 없음 • 의미 있는 마지막 업데이트가 몇 년 전임 |
| 수동 구현 (Stack Overflow에서 복사‑붙여넣기) | – | • 폴백을 제대로 처리하지 않음 • “복사됨!” 피드백에 대한 상태 관리가 없음 • Next.js나 Remix와 같은 프레임워크에서 SSR 문제 발생 |
use-clipboard-copy | – | • HTML 콘텐츠 지원이 없음 • 제한된 설정 옵션 |
결론: 나는 현대적이고, 경량이며, 완전한 무언가가 필요했다.
@samithahansaka/clipboard 소개
npm install @samithahansaka/clipboard
무엇이 다른가
| 기능 | 이 라이브러리 | 다른 라이브러리 |
|---|---|---|
| Hooks API | ✅ | Sometimes |
| Component API | ✅ | Sometimes |
| HTML/Rich content | ✅ | ❌ |
| TypeScript | ✅ First‑class | Partial |
| 번들 크기 | ~1 KB | 2‑5 KB |
| 의존성 | 0 | Varies |
| SSR‑안전 | ✅ | Often breaks |
기본 사용법
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 Docs나 이메일에 올바르게 붙여넣어지는 서식 있는 텍스트를 복사해야 하나요?
const { copy } = useCopyToClipboard();
// Copy both plain text **AND** HTML
copy({
text: 'Hello, World!', // Fallback for plain‑text apps
html: '**Hello, World!**' // Rich content for compatible apps
});
붙여넣을 때:
- Notepad / Terminal:
Hello, World! - Google Docs / Email: Hello, World! (굵게)
토스트 라이브러리와의 통합
모든 알림 라이브러리와 원활하게 작동합니다:
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>
);
}
Simple Cases에 대한 Component 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+):
전체 HTML 지원을 위해navigator.clipboard.write()를 사용합니다. - 다소 최신 브라우저:
일반 텍스트 복사를 위해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‑Safe
다음과 같은 Next.js, Remix, Gatsby 또는 모든 SSR 프레임워크와 바로 사용할 수 있습니다:
- “window is not defined” 오류가 발생하지 않습니다.
- 동적 import가 필요하지 않습니다.
- 그냥 일반적으로 사용하면 됩니다.
import { useCopyToClipboard } from '@samithahansaka/clipboard';
번들 크기
전체 라이브러리는 ~1 KB gzipped 입니다. 비교를 위해:
| Library | 크기 (gzipped) |
|---|---|
@samithahansaka/clipboard | ~1 KB |
react-copy-to-clipboard | ~2 KB |
use-clipboard-copy | ~3 KB |
