- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
在 Vue 3 应用中直接操作系统剪贴板往往要面对异步 API、权限门控和内容格式转换等繁琐细节。VueUse 提供的useClipboardItems正是面向浏览器原生 Clipboard API 的响应式封装:它允许你基于ClipboardItem读写任意格式的剪贴板内容,并自动维护content、copied等响应式状态,让"复制、读取、状态回显"的整套流程开箱即用。读完本文,你将掌握useClipboardItems的完整 API、与useClipboard的选型差异,以及它背后的源码实现原理,可以直接在项目中落地一个支持多种 MIME 类型的复制/粘贴组件。
什么是 useClipboardItems
useClipboardItems是 VueUse 核心包(packages/core)中面向浏览器 Clipboard API 的响应式工具函数,位于 packages/core/useClipboardItems/index.ts,并从packages/core/index.ts统一对外导出。
它的核心能力可以概括为三点:
- 响应式读取与写入:把
navigator.clipboard.read()与navigator.clipboard.write()封装成可复用的响应式状态; - 基于
ClipboardItem的多格式支持:可以复制图片、富文本、HTML、文本等任何浏览器ClipboardItem支持的内容; - 权限友好:剪贴板内容的读写受 Permissions API 门控,没有用户授权时不允许读取或修改剪贴板内容。
与 useClipboard 的区别:文本专用 vs 格式通用
VueUse 同时提供了两个剪贴板工具函数:useClipboard 与useClipboardItems。二者的选型差异非常清晰:
useClipboard是"纯文本"函数:它只处理字符串,内部会构造{ 'text/plain': value }的ClipboardItem进行写入(见 packages/core/useClipboard/index.ts),并额外支持legacy选项,在剪贴板 API 不可用时回退到document.execCommand('copy');useClipboardItems是ClipboardItem导向的函数:它直接接收并写入ClipboardItem实例,因此可以复制任何ClipboardItem支持的内容,包括图片、HTML、自定义 MIME 类型等。
简而言之:只需要复制/粘贴文本时用useClipboard;需要处理多格式内容时用useClipboardItems。
快速上手:一个完整的复制按钮
原文档给出了一份完整的 Vue 单文件组件示例,这里完整保留并补充注释,方便直接复制运行:
<script setup lang="ts"> import { useClipboardItems } from '@vueuse/core' const mime = 'text/plain' const source = ref([ new ClipboardItem({ [mime]: new Blob(['plain text'], { type: mime }), }) ]) const { content, copy, copied, isSupported } = useClipboardItems({ source }) </script> <template> <div v-if="isSupported"> <button @click="copy(source)"> <!-- 默认情况下,`copied` 会在 1.5 秒后自动重置 --> <span v-if="!copied">Copy</span> <span v-else>Copied!</span> </button> <p> Current copied: <code>{{ content || 'none' }}</code> </p> </div> <p v-else> Your browser does not support Clipboard API </p> </template>要点说明:
- 通过
new ClipboardItem({ [mime]: new Blob([...], { type: mime }) })构造剪贴板条目,mime声明内容的媒体类型; isSupported用于能力检测,在不支持 Clipboard API 的浏览器中渲染降级提示;- 传入
source选项后,copy()可以不传参数直接使用该 source 进行复制; copied是一个临时状态,默认 1.5 秒后自动恢复为false,很适合驱动按钮文案切换。
API 详解
useClipboardItems的完整类型定义在 packages/core/useClipboardItems/index.ts,下面逐项拆解。
选项(UseClipboardItemsOptions)
export interface UseClipboardItemsOptions<Source> extends ConfigurableNavigator { /** * 是否启用剪贴板读取 * * @default false */ read?: boolean /** * 复制数据源 */ source?: Source /** * 重置 `copied` ref 状态所需的毫秒数 * * @default 1500 */ copiedDuring?: number }| 选项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
read | boolean | false | 是否启用剪贴板读取。启用后会在用户执行copy/cut时自动更新content |
source | Source(一般为ClipboardItems) | 无 | 复制数据源。传入后copy()可以不传参数,内部通过toValue(source)解析 |
navigator | Navigator | window.navigator | 自定义navigator实例,来自ConfigurableNavigator,可用于 iframe 或测试环境注入 |
返回值(UseClipboardItemsReturn)
export interface UseClipboardItemsReturn<Optional> extends Supportable { content: Readonly<Ref<ClipboardItems>> copied: Readonly<ShallowRef<boolean>> copy: Optional extends true ? (content?: ClipboardItems) => Promise<void> : (text: ClipboardItems) => Promise<void> read: () => void }| 返回值 | 类型 | 说明 |
|---|---|---|
isSupported | ComputedRef<boolean> | 当前环境是否支持 Clipboard API(继承自Supportable) |
content | Readonly<Ref<ClipboardItems>> | 当前剪贴板内容(ClipboardItem[]),只读 |
copied | Readonly<ShallowRef<boolean>> | 复制成功标记,copiedDuring毫秒后自动重置 |
copy | (content?: ClipboardItems) => Promise<void> | 执行复制(是否可省略参数取决于是否传入source) |
read | () => void | 手动触发一次剪贴板读取,更新content |
重载签名与Optional泛型
源码通过两个重载区分"是否配置了 source":
export function useClipboardItems(options?: UseClipboardItemsOptions<undefined>): UseClipboardItemsReturn<false> export function useClipboardItems(options: UseClipboardItemsOptions<MaybeRefOrGetter<ClipboardItems>>): UseClipboardItemsReturn<true>- 不传
source时,copy必须显式传入ClipboardItems(Optional = false); - 传入
source(可以是值、ref或 getter,即MaybeRefOrGetter<ClipboardItems>)时,copy可以省略参数(Optional = true),内部自动解析 source。
源码级原理解析
useClipboardItems的实现非常精简(packages/core/useClipboardItems/index.ts),却完整覆盖了能力检测、写入、读取、状态复位四条链路,下面逐一展开。
1. 能力检测:isSupported
const isSupported = useSupported(() => (navigator && 'clipboard' in navigator))useSupported是 VueUse 通用的能力检测工具(packages/core/useSupported/index.ts),它基于useMounted的挂载状态构造一个computed,仅在组件挂载后计算Boolean(callback())。这意味着isSupported在 SSR 环境下为false——因为defaultNavigator = isClient ? window.navigator : undefined(见 packages/core/_configurable.ts),服务端没有navigator。这也是模板中用v-if="isSupported"做降级渲染的原因。
2. 写入链路:copy
async function copy(value = toValue(source)) { if (isSupported.value && value != null) { await navigator!.clipboard.write(value) content.value = value copied.value = true timeout.start() } }value默认取toValue(source),因此source可以是普通数组、ref或 getter,写入时统一解包;- 通过
navigator.clipboard.write(value)异步写入(参数是ClipboardItem[]); - 写入成功后同步更新
content与copied,并启动复位计时器。
3. copied 自动复位:useTimeoutFn
const timeout = useTimeoutFn(() => copied.value = false, copiedDuring, { immediate: false })useTimeoutFn(packages/shared/useTimeoutFn/index.ts)是对setTimeout的控制封装,{ immediate: false }表示创建时并不启动计时,而是由copy()在成功后调用timeout.start()。于是copied默认保持 1.5 秒后自动归零,无需手动清理。
4. 读取链路:updateContent 与事件监听
function updateContent() { if (isSupported.value) { navigator!.clipboard.read().then((items) => { content.value = items }) } } if (isSupported.value && read) { useEventListener(['copy', 'cut'], updateContent, { passive: true }) }read()返回值就是updateContent,调用它会触发一次navigator.clipboard.read(),把读到的ClipboardItem[]写入content;- 当选项
read: true时,函数还会通过useEventListener监听copy与cut事件(passive: true),用户在系统层面执行复制/剪切操作后自动刷新content; - 注意读取同样受权限门控,未授权时
clipboard.read()会失败。
5. 状态实现:shallowRef 与 shallowReadonly
const content = shallowRef<ClipboardItems>([]) const copied = shallowRef(false) // ... return { isSupported, content: shallowReadonly(content), copied: shallowReadonly(copied), copy, read: updateContent, }content与copied均采用shallowRef存储,对外通过shallowReadonly暴露只读视图,防止调用方直接篡改内部状态,保证状态流转始终经由copy/read两个入口。
读取剪贴板内容:结合 usePermission 与 getType
仓库自带的演示组件 packages/core/useClipboardItems/demo.vue 展示了读取方向的完整实践,核心思路是:用usePermission观察剪贴板读写权限,再对content中的每个ClipboardItem调用getType('text/plain')提取 Blob 文本:
import { useClipboardItems, usePermission } from '@vueuse/core' import { effect, shallowRef } from 'vue' const { content, isSupported, copy, read } = useClipboardItems() const permissionRead = usePermission('clipboard-read') const permissionWrite = usePermission('clipboard-write') effect(() => { Promise.all(content.value.map(item => item.getType('text/plain'))) .then(async (blobs) => { computedMimeType.value = blobs.map(blob => blob.type).join(', ') computedText.value = (await Promise.all(blobs.map(blob => blob.text()))).join(', ') }) })在模板中,它同时渲染出clipboard-read与clipboard-write两个权限状态(prompt/granted/denied),并提供"Copy"与"Manual read"两个按钮分别触发copy([...])和read():
<button @click="() => copy([createClipboardItems(input)])">Copy</button> <button @click="() => read()">Manual read</button>这套组合是读取方向的标准范式:usePermission('clipboard-read')负责权限提示与状态展示,read()负责拉取内容,item.getType(mime)负责按 MIME 提取对应数据。
注意事项
- 权限门控:剪贴板内容访问受 Permissions API 约束,读取或修改前需要用户授权(浏览器的权限提示),授权状态可用
usePermission(packages/core/usePermission)观察; - SSR 安全:由于
defaultNavigator仅在客户端存在(isClient ? window.navigator : undefined),服务端渲染时isSupported恒为false,务必用v-if="isSupported"或客户端挂载后执行相关逻辑; - 降级分支:模板中的
v-else分支用于不支持 Clipboard API 的浏览器,此时应提供替代方案(如需文本复制可考虑useClipboard的legacy回退); - 读取需要
read: true:默认不监听剪贴板事件,如需在用户复制/剪切后自动同步content,需显式开启read: true选项; content是ClipboardItem[]:直接渲染到页面通常需要像 demo 那样通过getType()提取并解析为文本或图片 URL 后再展示。
小结
useClipboardItems以极小的实现成本(核心仅约 40 行逻辑)把浏览器 Clipboard API 的读写、权限、事件监听与响应式状态管理整合为一个声明式接口:source+copy()解决写入,read+content解决读取,copiedDuring自动管理复制反馈,isSupported兜底能力降级。对于需要复制图片、富文本或自定义 MIME 内容的 Vue 3 应用,它是比useClipboard更贴合ClipboardItem能力的方案。
- 前端
【免费下载链接】vueuse
Collection of essential Vue Composition Utilities for Vue 3
相关推荐
VueUse useClipboard 完全指南:在 Vue 3 中响应式读写系统剪贴板
VueUse useClipboard 完全指南:在 Vue 3 中响应式读写系统剪贴板 导读 useClipboard 是 VueUse 对浏览器 Clipb
前端PhotoGIMP 安装教程:3 步让免费开源的 GIMP 长出 Photoshop 的手感
PhotoGIMP 安装教程:3 步让免费开源的 GIMP 长出 Photoshop 的手感 PhotoGIMP 是一款完全免费的 GIMP 3.0+ 补丁包:
CAT 进阶调优指南:采样策略、路由容灾与海量监控数据性能优化技巧
CAT 进阶调优指南:采样策略、路由容灾与海量监控数据性能优化技巧 CAT 是美团点评开源的实时应用监控平台,支持 Java、C/C++、Node.js、Pyt
可观测性指标监控告警APM后端链路追踪
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考