使用 react-use 的 useCopyToClipboard:在 React 中安全、优雅地实现复制到剪贴板
【免费下载链接】react-useReact Hooks — 👍项目地址: https://gitcode.com/gh_mirrors/re/react-use
本文围绕 react-use 仓库中的 useCopyToClipboard 文档 展开,系统讲解该 Hook 的 API 形态、返回状态语义、完整用法示例,并结合 源码实现 与 测试用例 剖析其内部的输入校验、异常处理与底层copy-to-clipboard依赖机制。读完本文,你将能直接在自己的 React 组件中集成"复制文本"能力,并正确区分value、error、noUserInteraction三个状态字段,从容处理复制失败与浏览器交互限制等边界场景。
Hook 是什么
useCopyToClipboard是 react-use 提供的一个副作用类(Side-effects)Hook,作用是将文本复制到用户的剪贴板。与直接调用浏览器 API 不同,它以 React 状态的形式把复制结果(成功复制的值、异常、是否需要用户交互)暴露给组件,便于 UI 层根据结果渲染提示,例如"已复制"或"复制失败"。
该 Hook 的底层并没有自行实现剪贴板写入,而是封装了 npm 包copy-to-clipboard(在 package.json 中声明为"copy-to-clipboard": "^3.3.1"),并将该库的完整 API 通过状态字段透出,这正是noUserInteraction字段的由来。
API 概览
useCopyToClipboard返回一个元组:当前状态对象与复制函数。
const [{value, error, noUserInteraction}, copyToClipboard] = useCopyToClipboard();value—— 成功复制到剪贴板的值;尚未复制任何内容时为undefined。error—— 尝试复制时捕获到的异常对象;复制成功时为undefined。noUserInteraction—— 布尔值,表示复制该值是否需要用户交互,用于透出底层copy-to-clipboard库的完整 API。copyToClipboard—— 复制函数,接收一个string(或可被转换为字符串的number),调用后触发剪贴板写入并更新上述状态。
状态对象的结构在 src/useCopyToClipboard.ts 中定义:
export interface CopyToClipboardState { value?: string; noUserInteraction: boolean; error?: Error; }注意状态对象以useSetState管理(见 src/useSetState.ts),复制调用内部通过Object.assign合并局部补丁,因此重复调用时value、error、noUserInteraction会按需更新。
快速上手
原文档给出了一个最小可运行的示例:输入框 + 按钮 + 状态反馈。以下示例直接继承自 docs/useCopyToClipboard.md,并在 Storybook 演示中也有对应版本(见 stories/useCopyToClipboard.story.tsx):
const Demo = () => { const [text, setText] = React.useState(''); const [state, copyToClipboard] = useCopyToClipboard(); return ( <div> <input value={text} onChange={e => setText(e.target.value)} /> <button type="button" onClick={() => copyToClipboard(text)}>copy text</button> {state.error ? <p>Unable to copy value: {state.error.message}</p> : state.value && <p>Copied {state.value}</p>} </div> ) }使用要点:
- 复制动作必须由用户事件触发:
copyToClipboard应放在按钮的onClick、键盘事件等用户交互回调中调用,浏览器对剪贴板写入普遍有用户手势要求。 - 渲染分支以
state.error为第一优先级:存在错误时优先展示错误信息(state.error.message),否则在state.value存在时展示"已复制"提示。 - 可直接在受控输入框中校验效果:Storybook 的 Demo 在复制成功后会额外渲染一个"Paste it in here to check"输入框,方便肉眼验证剪贴板内容是否真的写入。
返回值语义详解
value:被复制的值
- 复制成功后,
value为被规范化后的字符串(数字会被toString()转换,详见下文源码剖析),例如传入42时value为"42"。 - 从未复制过任何内容时,初始值为
undefined。 - 传入非法输入时,
value会被置为原始传入值(可能是对象等非字符串),这一点在测试中有明确断言(见 tests/useCopyToClipboard.test.ts)。
error:复制失败的异常
- 复制成功时
error为undefined。 - 输入类型非法、空字符串、或底层
copy-to-clipboard抛异常时,error会被设置为对应的Error对象。 - 常见 UI 写法是
state.error ? <p>Unable to copy value: {state.error.message}</p> : ...。
noUserInteraction:是否无需用户交互
这是从底层copy-to-clipboard库透出的标志位:
true表示复制操作不需要用户交互即可完成(例如通过execCommand之类的隐藏文本区域方式,或浏览器允许程序化写入的场景)。false表示复制需要用户交互(例如依赖navigator.clipboard权限弹窗的场景)。- 在未发生复制时,初始值为
true(见 src/useCopyToClipboard.ts)。
在 Storybook 演示中,UI 会据此渲染Copied xxx without/with user interaction,让开发者直观感知当前环境采用的复制路径(见 stories/useCopyToClipboard.story.tsx)。
源码原理剖析
理解状态语义后,深入 src/useCopyToClipboard.ts 可以看清每个字段背后真实的执行逻辑。整个 Hook 由三个关键部分组成:
1. 组合 useMountedState 与 useSetState
const isMounted = useMountedState(); const [state, setState] = useSetState<CopyToClipboardState>({ value: undefined, error: undefined, noUserInteraction: true, });useMountedState(见 src/useMountedState.ts)通过useRef记录组件挂载状态:useEffect挂载时置true、清理时置false,并返回一个get函数用于读取。useSetState(见 src/useSetState.ts)是 react-use 提供的类this.setState语义的状态管理,用Object.assign({}, prevState, patch)合并更新。
两者结合的价值在于:复制函数是异步路径(底层库可能涉及同步 execCommand 或异步 clipboard API),若组件已卸载仍调用setState会触发 React 警告或内存泄漏风险,因此每次复制前先执行if (!isMounted()) return;守卫(见 src/useCopyToClipboard.ts)。
2. 输入校验:只接受字符串与数字
复制函数内部有两道前置校验,全部通过后才真正调用底层库(见 src/useCopyToClipboard.ts):
- 类型校验:只有
typeof value === 'string'或typeof value === 'number'才被接受,否则构造错误Cannot copy typeof ${typeof value} to clipboard, must be a string,并跳过copy-to-clipboard的调用。 - 空字符串校验:
value === ''同样被视为非法,构造错误Cannot copy empty string to clipboard.。
这两类校验失败时都会执行:
setState({ value, error, noUserInteraction: true });即value保持原样、error记录失败原因、noUserInteraction保持true。同时在process.env.NODE_ENV === 'development'时通过console.error输出错误,方便开发期排查(见 src/useCopyToClipboard.ts)。
3. 规范化与底层写入
通过校验后:
normalizedValue = value.toString(); noUserInteraction = writeText(normalizedValue); setState({ value: normalizedValue, error: undefined, noUserInteraction });value.toString()将数字统一转换为字符串,保证value字段始终是字符串语义。writeText即从copy-to-clipboard导入的默认导出(import writeText from 'copy-to-clipboard',见 src/useCopyToClipboard.ts),其返回值正是noUserInteraction的来源。- 成功路径下
error被重置为undefined,避免上一次失败的错误残留。
4. 异常兜底
整个写入过程包裹在try/catch中(见 src/useCopyToClipboard.ts)。若底层库抛出异常,则:
setState({ value: normalizedValue, error, noUserInteraction });此时error为捕获到的异常对象,noUserInteraction保持上一次的取值(可能为undefined)。测试中用特殊输入'fake input causing exception in copy to clipboard'模拟了该路径,断言state.error与抛出的Error严格相等(见 tests/useCopyToClipboard.test.ts)。
测试如何验证行为
tests/useCopyToClipboard.test.ts 通过jest.mock('copy-to-clipboard')模拟底层库,覆盖了五类关键行为,可作为你使用该 Hook 时的行为契约参考:
| 测试场景 | 断言要点 |
|---|---|
| 正常复制字符串 | writeText被调用,state.value === 'test',noUserInteraction === true,无error |
| 非法输入(对象、空字符串) | writeText不被调用,state.value保持原始值,state.error已定义 |
| 底层库抛异常 | writeText收到原值,state.error与抛出的Error严格相等 |
| 组件卸载后调用 | 状态保持初始值(value/error为undefined,noUserInteraction为true),即isMounted守卫生效 |
| 开发环境非法输入 | console.error被调用(验证NODE_ENV === 'development'分支) |
这些断言从侧面印证了上文源码剖析的所有行为,特别是"卸载后不更新状态"与"开发环境错误提示"两个容易被忽视的细节。
使用注意事项
- 浏览器兼容与用户手势:复制功能依赖浏览器环境,
noUserInteraction字段会如实反映当前环境走的是无交互写入还是需要交互的路径;在要求严格手势的浏览器策略下,务必把复制调用绑定在用户事件回调中。 - 非法输入不会触及剪贴板:对象、布尔值、
null、undefined以及空字符串会在进入底层库之前被拦截并生成error,无需担心脏数据写入剪贴板。 - 卸载安全:Hook 内部已通过
useMountedState做了卸载守卫,异步回调中重复调用复制函数不会导致卸载后的setState警告。 - 错误展示:优先读取
state.error.message展示用户可读的失败原因;未复制任何内容时state.value为undefined,UI 需自行判断是否渲染"已复制"提示。 - 导出方式:该 Hook 以命名导出对外提供(见 src/index.ts),使用时通过
import { useCopyToClipboard } from 'react-use'引入。
小结
useCopyToClipboard用不到百行代码,把"复制到剪贴板"这一高频交互封装成符合 React 心智模型的状态式 API:以value承载结果、以error承载失败、以noUserInteraction透出底层交互模式。结合源码中的输入校验与卸载守卫,以及测试用例对每一条行为路径的锁定,你可以在自己的组件中放心复用它,并在复制失败、空输入、组件卸载等边界场景下获得稳定一致的反馈。
【免费下载链接】react-useReact Hooks — 👍项目地址: https://gitcode.com/gh_mirrors/re/react-use
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考