Vue选项式API与组合式API中ref行为统一实现方案
Vue组合式API复刻选项式API的单组件激活行为
需求
将Vue选项式API中ref的行为复刻到组合式API,实现点击按钮时仅激活被点击的单个组件/元素。
选项式API实现(单次激活单个组件)
在选项式API中,通过this.$refs.copy绑定DOM元素,能实现每次仅激活当前点击的组件,代码如下:
<template> <button ref="copy" class="copy-button"> <img v-if="state === 'copied'" src="/images/icons/verifiedCopy.svg" /> <img v-else src="/images/icons/unverifiedCopy.svg" /> </button> </template> <script> import Clipboard from 'clipboard' export default { data() { return { state: 'init' } }, mounted() { const copyCode = new Clipboard(this.$refs.copy, { target(trigger) { return trigger.parentElement?.previousElementSibling } }) copyCode.on('success', (event) => { event.clearSelection() this.state = 'copied' window.setTimeout(() => { this.state = 'init' }, 2000) }) } } </script>
组合式API当前问题(激活所有组件)
当前组合式API代码会导致所有CopyButton组件同时激活,问题代码如下:
<template> <button ref="copy" class="copy-button"> <img v-if="state === 'copied'" src="/images/icons/verifiedCopy.svg" /> <img v-else src="/images/icons/unverifiedCopy.svg" /> </button> </template> <script setup lang="ts"> import Clipboard from 'clipboard' import { ref, onMounted } from 'vue' const state = useState(() => 'init') const copy = ref<HTMLElement | null>(null) onMounted(() => { setTimeout(() => { const copyCode = new Clipboard(copy.value!, { target(trigger) { return trigger.parentElement?.previousElementSibling! } }) copyCode.on('success', (event) => { event.clearSelection() state.value = 'copied' window.setTimeout(() => { state.value = 'init' }, 2000) }) }, 55) }) </script>
解决方案
问题核心是误用了全局状态的useState,导致所有组件共享同一个状态值。修正方案如下:
<template> <button ref="copy" class="copy-button"> <img v-if="state === 'copied'" src="/images/icons/verifiedCopy.svg" /> <img v-else src="/images/icons/unverifiedCopy.svg" /> </button> </template> <script setup lang="ts"> import Clipboard from 'clipboard' import { ref, onMounted, onUnmounted } from 'vue' // 用Vue内置ref创建组件独立状态,替代全局的useState const state = ref<'init' | 'copied'>('init') const copy = ref<HTMLElement | null>(null) let copyCode: Clipboard | null = null onMounted(() => { if (copy.value) { copyCode = new Clipboard(copy.value, { target(trigger) { return trigger.parentElement?.previousElementSibling } }) copyCode.on('success', (event) => { event.clearSelection() state.value = 'copied' window.setTimeout(() => { state.value = 'init' }, 2000) }) } }) // 组件卸载时销毁Clipboard实例,避免内存泄漏 onUnmounted(() => { copyCode?.destroy() }) </script>
关键修正点
- 替换状态创建方式:把全局共享的
useState换成Vue内置的ref,确保每个组件实例拥有独立的state,点击时只会修改当前组件的状态。 - 移除不必要延迟:
onMounted执行时ref已经完成DOM绑定,不需要额外的setTimeout初始化Clipboard。 - 添加清理逻辑:在
onUnmounted中销毁Clipboard实例,避免内存泄漏,这是选项式API实现中缺失的优化。
内容的提问来源于stack exchange,提问作者DAYVSON SILVINO DA SILVA
相关产品推荐
相关产品推荐

