由于目前维护的项目比较老,所以想记录下在 Vue 2 项目中使用 VueUse (@vueuse/core
) 这个强大的组合式函数库, 写下这篇文章。
VueUse 主要是为 Vue 3 的 Composition API 设计的,但幸运的是,Vue 官方提供了一个插件 @vue/composition-api
,它将 Composition API 的核心功能引入到了 Vue 2.x 中。这使得我们可以在 Vue 2 项目里享受到 VueUse 带来的便利。
核心步骤:
- 安装依赖: 安装
@vue/composition-api
和@vueuse/core
。 - 注册插件: 在你的 Vue 应用入口文件 (
main.js
或main.ts
) 中注册@vue/composition-api
插件。 - 使用 Composition API: 在 Vue 组件中使用
setup()
函数来组织逻辑。 - 引入和使用 VueUse 函数: 在
setup()
函数中引入并调用所需的 VueUse 函数。
下面我们将逐步展开,并提供详尽的代码示例。
1. 安装依赖
bash
# 使用 npm
npm install @vue/composition-api @vueuse/core --save
# 或者使用 yarn
yarn add @vue/composition-api @vueuse/core
2. 注册插件 (main.js
)
你需要在使用 Vue 的任何其他插件或实例化 Vue 应用之前,先 use
这个 Composition API 插件。
javascript
// src/main.js
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api'; // 引入插件
import App from './App.vue';
// !!必须在使用其他插件或 new Vue() 之前调用 Vue.use() !!
Vue.use(VueCompositionAPI); // 注册插件
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
3. 基本使用模式 (在 Vue 组件中)
在 Vue 2 组件中,你需要使用 @vue/composition-api
提供的 defineComponent
(推荐,尤其是在使用 TypeScript 时) 或直接在选项对象中添加 setup()
方法。setup
函数在 beforeCreate
和 created
生命周期钩子之前执行。它接收 props
和 context
(包含 attrs
, slots
, emit
) 作为参数。
从 setup
函数返回的对象会暴露给组件的模板和选项式 API (如 methods
, computed
, data
等)。
vue
<template>
<div>
<!-- 模板中可以直接使用 setup 返回的 ref 或 reactive 对象 -->
<p>计数器: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { defineComponent, ref } from '@vue/composition-api';
export default defineComponent({
// setup 函数是 Composition API 的入口
setup() {
// 使用 ref 创建一个响应式引用
const count = ref(0);
// 定义一个方法
const increment = () => {
count.value++; // 修改 ref 的值需要通过 .value
};
// 必须返回需要在模板或其他选项中使用的变量和方法
return {
count,
increment,
};
},
// 你仍然可以像以前一样使用选项式 API,
// 并且可以访问 setup 返回的内容 (通过 this)
mounted() {
console.log('组件挂载,计数器初始值:', this.count); // 可以访问 count
},
methods: {
logFromOptionsAPI() {
console.log('从选项式 API 访问计数器:', this.count); // 也可以访问
this.increment(); // 也可以调用 setup 中返回的方法
}
}
});
</script>
4. 常用 VueUse 函数在 Vue 2 中的使用示例
现在,我们将展示一些常用的 VueUse 函数如何在集成了 @vue/composition-api
的 Vue 2 项目中使用。
示例 1: useMouse
- 跟踪鼠标位置
useMouse
可以轻松获取鼠标在页面或特定元素内的实时坐标。
vue
<!-- src/components/UseMouseExample.vue -->
<template>
<div class="mouse-tracker-container">
<h3>useMouse 示例</h3>
<p>跟踪整个页面的鼠标位置:</p>
<pre>{{ JSON.stringify(pageMouse, null, 2) }}</pre>
<p>X: {{ pageMouse.x }}, Y: {{ pageMouse.y }}</p>
<p>Source Type: {{ pageMouse.sourceType }}</p>
<hr>
<p>跟踪下方蓝色区域内的鼠标位置 (相对该区域):</p>
<div ref="targetArea" class="target-area">
<p>将鼠标移到这里</p>
<pre>相对位置: {{ JSON.stringify(elementMouse, null, 2) }}</pre>
<p>Rel X: {{ elementMouse.x }}, Rel Y: {{ elementMouse.y }}</p>
</div>
<hr>
<p>只在按下左键时跟踪:</p>
<pre>{{ JSON.stringify(leftClickMouse, null, 2) }}</pre>
<p>按下左键移动试试看...</p>
</div>
</template>
<script>
import { defineComponent, ref, reactive } from '@vue/composition-api';
import { useMouse, useMouseInElement } from '@vueuse/core';
export default defineComponent({
name: 'UseMouseExample',
setup() {
// 1. 跟踪整个页面的鼠标位置
// useMouse 返回一个包含 x, y, sourceType 的响应式对象
const pageMouse = reactive(useMouse()); // 使用 reactive 包裹,方便模板直接使用
// 2. 跟踪特定元素内的鼠标位置
const targetArea = ref(null); // 创建一个 ref 来引用 DOM 元素
// useMouseInElement 需要一个目标元素的 ref
// 它返回更详细的信息,包括相对元素的位置 (x, y), 元素边界,是否在元素内等
const elementMouseState = reactive(useMouseInElement(targetArea));
// 为了简洁,我们只返回关心的部分
const elementMouse = reactive({
x: elementMouseState.x,
y: elementMouseState.y,
isOutside: elementMouseState.isOutside,
elementX: elementMouseState.elementX, // 元素左上角相对于视口的 X
elementY: elementMouseState.elementY, // 元素左上角相对于视口的 Y
elementPositionX: elementMouseState.elementPositionX, // 元素左边缘相对于文档的 X
elementPositionY: elementMouseState.elementPositionY, // 元素上边缘相对于文档的 Y
elementHeight: elementMouseState.elementHeight, // 元素高度
elementWidth: elementMouseState.elementWidth // 元素宽度
});
// 3. 仅在按下鼠标左键时跟踪 (高级用法)
const leftClickMouse = reactive(useMouse({ touch: false, type: 'page', mouseButton: 'left' }));
// 必须返回所有需要在模板中使用的数据和 ref
return {
pageMouse,
targetArea, // 需要把 ref 返回给模板,以便关联 DOM 元素
elementMouse,
leftClickMouse,
};
},
});
</script>
<style scoped>
.mouse-tracker-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
.target-area {
width: 300px;
height: 200px;
background-color: lightblue;
padding: 10px;
margin-top: 10px;
position: relative; /* useMouseInElement 经常需要这个 */
border: 1px solid blue;
}
pre {
background-color: #f5f5f5;
padding: 8px;
border-radius: 4px;
font-size: 0.9em;
overflow-x: auto;
}
hr {
margin: 20px 0;
}
</style>
示例 2: useLocalStorage
- 与 LocalStorage 同步的 Ref
useLocalStorage
创建一个 ref,它的值会自动与浏览器的 LocalStorage 同步。
vue
<!-- src/components/UseLocalStorageExample.vue -->
<template>
<div class="local-storage-container">
<h3>useLocalStorage 示例</h3>
<p>这个输入框的值会实时同步到 LocalStorage (键: 'my-vue2-key')。</p>
<p>关闭浏览器标签页或刷新页面后,值仍然存在。</p>
<label for="storageInput">输入值:</label>
<input id="storageInput" type="text" v-model="syncedData" placeholder="在此输入内容...">
<p>当前 LocalStorage ('my-vue2-key') 中的值:</p>
<pre>{{ syncedData || '(空)' }}</pre>
<button @click="resetData">重置/清空数据</button>
<hr>
<h4>使用对象和序列化器:</h4>
<p>用户信息 (存储为 JSON 字符串):</p>
<pre>{{ JSON.stringify(userInfo, null, 2) }}</pre>
<label>用户名:</label>
<input v-model="userInfo.name" />
<label>年龄:</label>
<input type="number" v-model.number="userInfo.age" />
<button @click="clearUserInfo">清空用户信息</button>
<p><small>刷新页面,用户信息也会保留。</small></p>
</div>
</template>
<script>
import { defineComponent } from '@vue/composition-api';
import { useLocalStorage } from '@vueuse/core';
export default defineComponent({
name: 'UseLocalStorageExample',
setup() {
// 1. 基本用法:同步一个字符串
// 第一个参数是 LocalStorage 的键名
// 第二个参数是初始值(如果 localStorage 中没有对应项)
const syncedData = useLocalStorage('my-vue2-key', '默认值');
const resetData = () => {
syncedData.value = ''; // 设置为空字符串来清空
// 或者 syncedData.value = null; 如果你希望 localStorage 中存储 'null'
// 或者 syncedData.value = undefined; 会从 localStorage 移除该项
};
// 2. 使用对象和自定义序列化器 (默认使用 JSON)
// 如果初始值是一个对象或数组,useLocalStorage 会自动使用 JSON.stringify/parse
const userInfo = useLocalStorage('user-info-vue2', {
name: '匿名用户',
age: 18,
preferences: { theme: 'light' }
});
const clearUserInfo = () => {
// 设置为 null 或 undefined 都会移除 localStorage 中的项
userInfo.value = null;
// 如果想置为初始空对象结构,可以:
// userInfo.value = { name: '', age: 0, preferences: {} };
};
// 返回需要在模板中使用的数据
return {
syncedData,
resetData,
userInfo,
clearUserInfo
};
},
mounted() {
// 可以在这里观察 localStorage 的变化
window.addEventListener('storage', (event) => {
if (event.key === 'my-vue2-key' || event.key === 'user-info-vue2') {
console.log(`Storage changed for key "${event.key}":`, event.newValue);
// 注意:useLocalStorage 已经处理了响应式更新,这里只是演示监听原生事件
}
});
}
});
</script>
<style scoped>
.local-storage-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
label {
margin-right: 5px;
font-weight: bold;
}
input[type="text"], input[type="number"] {
margin-right: 10px;
padding: 5px;
border: 1px solid #ddd;
}
button {
margin-left: 10px;
padding: 5px 10px;
cursor: pointer;
}
pre {
background-color: #f5f5f5;
padding: 8px;
border-radius: 4px;
margin-top: 10px;
white-space: pre-wrap;
word-wrap: break-word;
}
hr {
margin: 20px 0;
}
small {
display: block;
margin-top: 5px;
color: #666;
}
</style>
示例 3: useNetwork
- 检测网络状态
useNetwork
提供关于用户网络连接的信息,如是否在线、连接类型等。
vue
<!-- src/components/UseNetworkExample.vue -->
<template>
<div class="network-status-container">
<h3>useNetwork 示例</h3>
<p>实时检测你的网络连接状态。</p>
<div v-if="isSupported">
<p :class="{ 'online': isOnline, 'offline': !isOnline }">
当前状态: {{ isOnline ? '在线 (Online)' : '离线 (Offline)' }}
</p>
<p v-if="offlineAt">上次离线时间: {{ offlineAt ? new Date(offlineAt).toLocaleString() : 'N/A' }}</p>
<p>网络类型 (估计): {{ networkType }}</p>
<p>有效连接类型 (估计): {{ effectiveType }}</p>
<p>下行最大比特率 (Mbps, 估计): {{ downlinkMax === Infinity ? '未知' : downlinkMax }}</p>
<p>往返时间 (RTT, ms, 估计): {{ rtt }}</p>
<p>数据保护模式是否开启: {{ saveData ? '是' : '否' }}</p>
<p><small>提示: 尝试在浏览器开发者工具中切换在线/离线状态,观察变化。</small></p>
</div>
<div v-else>
<p>你的浏览器不支持 Network Information API。</p>
</div>
<button @click="forceUpdateInfo">手动强制更新信息 (一般不需要)</button>
<pre>原始数据: {{ JSON.stringify(networkState, null, 2) }}</pre>
</div>
</template>
<script>
import { defineComponent, reactive, computed } from '@vue/composition-api';
import { useNetwork } from '@vueuse/core';
export default defineComponent({
name: 'UseNetworkExample',
setup() {
// useNetwork 返回一个包含网络状态信息的响应式对象
const networkState = reactive(useNetwork());
// 为了模板可读性,可以创建一些计算属性或直接解构 (如果用 reactive 包裹了)
const isOnline = computed(() => networkState.isOnline);
const offlineAt = computed(() => networkState.offlineAt);
const downlinkMax = computed(() => networkState.downlinkMax);
const effectiveType = computed(() => networkState.effectiveType);
const rtt = computed(() => networkState.rtt);
const saveData = computed(() => networkState.saveData);
const networkType = computed(() => networkState.type); // `type` 是原生属性名
const isSupported = computed(() => networkState.isSupported);
// VueUse 通常会自动更新,但如果需要手动触发,可以这样做 (虽然很少需要)
const forceUpdateInfo = () => {
// useNetwork 内部没有直接暴露 update 方法,它是事件驱动的
// 这个按钮主要是为了演示目的,实际中网络变化会自动触发更新
console.log('手动检查并不能直接触发 useNetwork 更新,它是基于浏览器事件的。');
// 如果你想强制重新评估,可以改变依赖项,但这不适用于 useNetwork
};
return {
networkState, // 也可以直接返回整个对象
isOnline,
offlineAt,
downlinkMax,
effectiveType,
rtt,
saveData,
networkType,
isSupported,
forceUpdateInfo,
};
},
});
</script>
<style scoped>
.network-status-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
.online {
color: green;
font-weight: bold;
}
.offline {
color: red;
font-weight: bold;
}
pre {
background-color: #f5f5f5;
padding: 8px;
border-radius: 4px;
margin-top: 10px;
font-size: 0.9em;
overflow-x: auto;
}
small {
display: block;
margin-top: 5px;
color: #666;
}
button {
margin-top: 10px;
padding: 5px 10px;
}
</style>
示例 4: useClipboard
- 与剪贴板交互
useClipboard
提供了读取和写入系统剪贴板的功能。注意:剪贴板 API 通常需要用户交互(如点击按钮)才能触发,并且可能需要 HTTPS 环境或特定的浏览器权限。
vue
<!-- src/components/UseClipboardExample.vue -->
<template>
<div class="clipboard-container">
<h3>useClipboard 示例</h3>
<p><small>注意:剪贴板操作可能需要用户授权或在安全上下文 (HTTPS) 中运行。</small></p>
<div v-if="isSupported">
<h4>写入剪贴板</h4>
<input type="text" v-model="sourceText" placeholder="要复制的文本" />
<button @click="copyText" :disabled="!sourceText">
{{ copied ? '已复制!' : '复制' }}
</button>
<span v-if="copyError" class="error-text">复制失败: {{ copyError.message }}</span>
<h4>读取剪贴板 (需要权限)</h4>
<button @click="readFromClipboard">读取剪贴板内容</button>
<p>读取到的内容:</p>
<pre>{{ clipboardText || '(尚未读取或权限不足)' }}</pre>
<span v-if="readError" class="error-text">读取失败: {{ readError.message }}</span>
<hr>
<h4>遗留模式 (不支持 Navigator API 时备用)</h4>
<p>文本内容: <code>{{ legacyText }}</code></p>
<button @click="copyLegacy" :disabled="legacyCopied">
{{ legacyCopied ? '已复制 (Legacy)!' : '复制 (Legacy)' }}
</button>
</div>
<div v-else>
<p>你的浏览器不支持 Clipboard API。</p>
</div>
</div>
</template>
<script>
import { defineComponent, ref, watch } from '@vue/composition-api';
import { useClipboard } from '@vueuse/core';
export default defineComponent({
name: 'UseClipboardExample',
setup() {
const sourceText = ref('你好,VueUse in Vue 2!');
const clipboardText = ref(''); // 用于存储读取到的内容
const copyError = ref(null);
const readError = ref(null);
// 1. 基本复制功能
// useClipboard 返回响应式的 text, copied, isSupported, 和 copy 方法
const { text, copy, copied, isSupported, error } = useClipboard({ source: sourceText });
// 监视复制操作的错误
watch(error, (newError) => {
copyError.value = newError;
if (newError) console.error('复制时出错:', newError);
});
const copyText = async () => {
copyError.value = null; // 重置错误状态
// copy(sourceText.value) // 也可以不通过 source 选项,直接调用 copy 方法传递值
await copy(); // 调用 copy 方法,它会复制 source (即 sourceText) 的当前值
// copied ref 会自动更新
};
// 2. 读取剪贴板 (这是一个异步操作,可能需要用户权限)
// navigator.clipboard.readText() 返回 Promise
const readFromClipboard = async () => {
readError.value = null; // 重置错误状态
try {
// 注意:useClipboard 本身不直接提供 read 方法,需要使用原生的 API
if (navigator.clipboard && navigator.clipboard.readText) {
clipboardText.value = await navigator.clipboard.readText();
console.log('剪贴板内容已读取:', clipboardText.value);
} else {
throw new Error('浏览器不支持读取剪贴板或缺少权限。');
}
} catch (err) {
readError.value = err;
clipboardText.value = ''; // 清空显示
console.error('读取剪贴板失败:', err);
}
};
// 3. 遗留模式示例 (当 navigator.clipboard 不可用时,useClipboard 会尝试 document.execCommand)
const legacyText = ref('这是用于旧版复制的文本');
// 可以为不同的复制操作创建不同的 useClipboard 实例
const { copy: copyLegacy, copied: legacyCopied } = useClipboard({ source: legacyText, legacy: true });
return {
sourceText,
copyText,
copied, // 显示复制状态
isSupported,
copyError,
clipboardText,
readFromClipboard,
readError,
legacyText,
copyLegacy,
legacyCopied,
};
},
});
</script>
<style scoped>
.clipboard-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
input[type="text"] {
margin-right: 10px;
padding: 5px;
min-width: 200px;
}
button {
padding: 5px 10px;
cursor: pointer;
margin-right: 10px;
}
button:disabled {
cursor: not-allowed;
opacity: 0.6;
}
pre {
background-color: #f5f5f5;
padding: 8px;
border-radius: 4px;
margin-top: 10px;
white-space: pre-wrap;
word-wrap: break-word;
}
small {
display: block;
margin-bottom: 10px;
color: #666;
}
.error-text {
color: red;
font-size: 0.9em;
margin-left: 10px;
}
hr {
margin: 20px 0;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 3px;
}
</style>
示例 5: useDebounceFn
和 useThrottleFn
- 函数防抖与节流
这两个函数用于控制函数的执行频率,常用于性能优化,如处理输入事件、窗口大小调整等。
useDebounceFn
: 在事件触发后等待指定时间,若期间没有再次触发,则执行函数。useThrottleFn
: 在指定时间间隔内最多执行一次函数。
vue
<!-- src/components/UseDebounceThrottleExample.vue -->
<template>
<div class="debounce-throttle-container">
<h3>useDebounceFn & useThrottleFn 示例</h3>
<h4>防抖 (Debounce) - 500ms</h4>
<p>输入时,只有停止输入 500ms 后才会触发日志记录。</p>
<input type="text" v-model="debouncedInput" @input="handleDebouncedInput" placeholder="输入内容以测试防抖...">
<p>最后触发的输入值 (防抖后): <pre>{{ lastDebouncedValue }}</pre></p>
<p>触发次数: {{ debouncedCallCount }}</p>
<hr>
<h4>节流 (Throttle) - 1000ms</h4>
<p>快速点击按钮,函数最多每 1000ms 执行一次。</p>
<button @click="throttledClickHandler">点击我 (节流)</button>
<p>节流函数触发次数: {{ throttledCallCount }}</p>
<p>最后触发时间: {{ lastThrottledTime || 'N/A' }}</p>
<hr>
<h4>节流 (Throttle) - 带有尾随调用 (trailing: true)</h4>
<p>快速点击按钮,函数最多每 1000ms 执行一次,并且在最后一次触发后如果处于冷却期,还会再执行一次。</p>
<button @click="throttledTrailingClickHandler">点击我 (节流 + 尾随)</button>
<p>节流 (尾随) 函数触发次数: {{ throttledTrailingCallCount }}</p>
<p>最后触发时间: {{ lastThrottledTrailingTime || 'N/A' }}</p>
</div>
</template>
<script>
import { defineComponent, ref } from '@vue/composition-api';
import { useDebounceFn, useThrottleFn } from '@vueuse/core';
export default defineComponent({
name: 'UseDebounceThrottleExample',
setup() {
// --- 防抖 ---
const debouncedInput = ref('');
const lastDebouncedValue = ref('');
const debouncedCallCount = ref(0);
// 创建防抖函数
const debouncedLog = useDebounceFn((value) => {
console.log('防抖触发:', value);
lastDebouncedValue.value = value;
debouncedCallCount.value++;
}, 500); // 500ms 延迟
const handleDebouncedInput = (event) => {
// 每次输入都调用防抖函数,但它内部会处理延迟执行
debouncedLog(event.target.value);
};
// --- 节流 ---
const throttledCallCount = ref(0);
const lastThrottledTime = ref(null);
// 创建节流函数
const throttledHandler = useThrottleFn(() => {
console.log('节流触发!');
throttledCallCount.value++;
lastThrottledTime.value = new Date().toLocaleTimeString();
}, 1000); // 1000ms 间隔
const throttledClickHandler = () => {
// 每次点击都调用节流函数,但它内部会限制执行频率
throttledHandler();
};
// --- 节流 (带尾随调用) ---
const throttledTrailingCallCount = ref(0);
const lastThrottledTrailingTime = ref(null);
// 创建节流函数 (trailing: true)
const throttledTrailingHandler = useThrottleFn(() => {
console.log('节流 (带尾随) 触发!');
throttledTrailingCallCount.value++;
lastThrottledTrailingTime.value = new Date().toLocaleTimeString();
}, 1000, true); // 第三个参数 true 开启尾随调用
const throttledTrailingClickHandler = () => {
throttledTrailingHandler();
};
return {
debouncedInput,
handleDebouncedInput,
lastDebouncedValue,
debouncedCallCount,
throttledClickHandler,
throttledCallCount,
lastThrottledTime,
throttledTrailingClickHandler,
throttledTrailingCallCount,
lastThrottledTrailingTime
};
},
});
</script>
<style scoped>
.debounce-throttle-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
input[type="text"] {
margin-right: 10px;
padding: 5px;
width: 250px;
}
button {
padding: 8px 15px;
cursor: pointer;
}
pre {
background-color: #f5f5f5;
padding: 8px;
border-radius: 4px;
margin-top: 5px;
display: inline-block; /* 让 pre 不占满整行 */
min-height: 1.5em; /* 给点高度防止空的时候塌陷 */
vertical-align: middle; /* 与旁边的文字对齐 */
}
hr {
margin: 25px 0;
}
p {
margin-bottom: 10px;
}
</style>
示例 6: useDateFormat
和 useNow
- 时间格式化与实时时间
useNow
: 提供一个响应式的当前时间 ref,可以配置更新间隔。useDateFormat
: 格式化日期或时间戳。
vue
<!-- src/components/UseTimeExample.vue -->
<template>
<div class="time-container">
<h3>useNow & useDateFormat 示例</h3>
<h4>实时时间 (useNow)</h4>
<p>useNow 默认每秒更新一次。</p>
<p>当前时间 (原始): {{ now }}</p>
<p>当前时间 (格式化): {{ formattedNow }}</p>
<hr>
<h4>不同更新频率的 useNow</h4>
<p>每 5 秒更新一次: {{ slowNow }} (格式化: {{ formattedSlowNow }})</p>
<p>高频更新 (每帧动画): {{ fastNow }} (格式化: {{ formattedFastNow }})</p>
<hr>
<h4>使用 useDateFormat 格式化固定日期</h4>
<p>固定日期: {{ fixedDate }}</p>
<p>格式 'YYYY-MM-DD HH:mm:ss': {{ format1 }}</p>
<p>格式 'dddd, MMMM D, YYYY': {{ format2 }}</p>
<p>格式 'short': {{ format3 }}</p>
<p>格式 '自定义 [Year:] YYYY [Month:] MM': {{ formatCustom }}</p>
<p>格式化时间戳 {{ timestamp }}: {{ formatTimestamp }}</p>
<p>格式化响应式日期 {{ reactiveDate }}: {{ formatReactiveDate }}</p>
<button @click="changeReactiveDate">改变响应式日期</button>
</div>
</template>
<script>
import { defineComponent, ref, computed } from '@vue/composition-api';
import { useNow, useDateFormat } from '@vueuse/core';
export default defineComponent({
name: 'UseTimeExample',
setup() {
// --- useNow ---
// 1. 默认更新 (每秒)
const now = useNow();
// 结合 useDateFormat 格式化 useNow 返回的响应式时间
const formattedNow = useDateFormat(now, 'YYYY-MM-DD HH:mm:ss');
// 2. 自定义更新间隔 (每 5000ms)
const slowNow = useNow({ interval: 5000 });
const formattedSlowNow = useDateFormat(slowNow, 'HH:mm:ss');
// 3. 使用 requestAnimationFrame 更新 (高频)
const fastNow = useNow({ interval: 'requestAnimationFrame' });
const formattedFastNow = useDateFormat(fastNow, 'HH:mm:ss.SSS'); // 显示毫秒
// --- useDateFormat ---
const fixedDate = new Date(2024, 0, 1, 10, 30, 0); // 2024年1月1日 10:30:00 (注意月份从0开始)
const timestamp = 1678886400000; // 一个时间戳 (例如: 2023-03-15T13:20:00Z)
const reactiveDate = ref(new Date()); // 一个响应式的 Date 对象
// 格式化固定日期
const format1 = useDateFormat(fixedDate, 'YYYY-MM-DD HH:mm:ss');
const format2 = useDateFormat(fixedDate, 'dddd, MMMM D, YYYY'); // 星期几, 月份全称 日, 年份
const format3 = useDateFormat(fixedDate, 'short'); // 预设格式
// 自定义格式,使用方括号 [] 来转义字符
const formatCustom = useDateFormat(fixedDate, '[Year:] YYYY [Month:] MM');
// 格式化时间戳
const formatTimestamp = useDateFormat(timestamp, 'YYYY/MM/DD');
// 格式化响应式日期 (当 reactiveDate.value 变化时,格式化结果也会自动更新)
const formatReactiveDate = useDateFormat(reactiveDate, 'DD-MMM-YYYY HH:mm');
const changeReactiveDate = () => {
reactiveDate.value = new Date(Date.now() + 24 * 60 * 60 * 1000); // 设置为明天的当前时间
};
return {
now,
formattedNow,
slowNow,
formattedSlowNow,
fastNow,
formattedFastNow,
fixedDate, // 仅用于显示原始日期
timestamp, // 仅用于显示原始时间戳
format1,
format2,
format3,
formatCustom,
formatTimestamp,
reactiveDate, // 原始响应式日期 ref
formatReactiveDate,
changeReactiveDate,
};
},
});
</script>
<style scoped>
.time-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
p {
margin-bottom: 8px;
}
hr {
margin: 20px 0;
}
button {
padding: 5px 10px;
margin-top: 5px;
}
</style>
示例 7: useFullscreen
- 控制元素全屏
useFullscreen
允许你轻松地进入或退出一个元素(或整个页面)的全屏模式。
vue
<!-- src/components/UseFullscreenExample.vue -->
<template>
<div class="fullscreen-container">
<h3>useFullscreen 示例</h3>
<p><small>全屏 API 的可用性和行为可能因浏览器而异。</small></p>
<div v-if="isFullscreenSupported">
<h4>控制整个页面全屏</h4>
<button @click="togglePageFullscreen">
{{ isPageFullscreen ? '退出页面全屏' : '进入页面全屏' }}
</button>
<p>当前页面是否全屏: {{ isPageFullscreen }}</p>
<hr>
<h4>控制特定元素全屏</h4>
<p>点击下方按钮,让这个蓝色区域全屏显示。</p>
<div ref="elementTarget" class="fullscreen-element">
这是一个可以全屏的目标元素。
<br>
<button @click="toggleElementFullscreen">
{{ isElementFullscreen ? '退出元素全屏' : '进入元素全屏' }}
</button>
<p style="font-size: 0.9em; margin-top: 10px;">该元素是否全屏: {{ isElementFullscreen }}</p>
</div>
<!-- 显示原始状态(调试用) -->
<!-- <pre>页面状态: {{ pageFsState }}</pre> -->
<!-- <pre>元素状态: {{ elementFsState }}</pre> -->
</div>
<div v-else>
<p>你的浏览器不支持 Fullscreen API。</p>
</div>
</div>
</template>
<script>
import { defineComponent, ref } from '@vue/composition-api';
import { useFullscreen } from '@vueuse/core';
export default defineComponent({
name: 'UseFullscreenExample',
setup() {
// 1. 控制整个页面 (document.documentElement)
// 不传参数时,默认目标是 document.documentElement
const {
isSupported: isPageFsSupported, // 重命名以区分
isFullscreen: isPageFullscreen,
enter: enterPageFullscreen,
exit: exitPageFullscreen,
toggle: togglePageFullscreen,
// ...还有 error 事件等,这里省略
} = useFullscreen(); // 如果需要捕获错误,可以添加 { onError: (e) => console.error(e) }
// 2. 控制特定元素
const elementTarget = ref(null); // Ref 引用目标 DOM 元素
const {
isSupported: isElementFsSupported, // 一般与页面支持情况相同
isFullscreen: isElementFullscreen,
enter: enterElementFullscreen,
exit: exitElementFullscreen,
toggle: toggleElementFullscreen,
} = useFullscreen(elementTarget); // 将 ref 传入 useFullscreen
// 通常 isSupported 对两者来说是相同的,所以我们可以用一个统一的
const isFullscreenSupported = isPageFsSupported; // 或 isElementFsSupported
// 你也可以直接返回整个 useFullscreen 返回的对象,方便调试
// const pageFsState = useFullscreen();
// const elementFsState = useFullscreen(elementTarget);
return {
isFullscreenSupported,
isPageFullscreen,
togglePageFullscreen,
elementTarget, // 必须返回 ref 以便模板关联
isElementFullscreen,
toggleElementFullscreen,
// pageFsState, // (调试用)
// elementFsState, // (调试用)
};
},
});
</script>
<style scoped>
.fullscreen-container {
padding: 20px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
.fullscreen-element {
width: 300px;
height: 150px;
padding: 20px;
background-color: lightcoral;
color: white;
margin-top: 10px;
border: 2px solid darkred;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
/* 当元素全屏时的特殊样式(可选) */
.fullscreen-element:fullscreen {
background-color: coral;
font-size: 1.5em;
}
/* 针对 Webkit 浏览器 (如 Chrome, Safari) */
.fullscreen-element:-webkit-full-screen {
background-color: coral;
font-size: 1.5em;
}
/* 针对 Firefox */
.fullscreen-element:-moz-full-screen {
background-color: coral;
font-size: 1.5em;
}
/* 针对 Edge 和 IE */
.fullscreen-element:-ms-fullscreen {
background-color: coral;
font-size: 1.5em;
}
button {
padding: 8px 15px;
cursor: pointer;
margin-top: 10px;
}
hr {
margin: 20px 0;
}
small {
display: block;
margin-bottom: 10px;
color: #666;
}
pre {
background-color: #f5f5f5;
padding: 8px;
border-radius: 4px;
margin-top: 10px;
font-size: 0.9em;
overflow-x: auto;
}
</style>
5. 在App.vue
中使用这些组件
vue
<!-- src/App.vue -->
<template>
<div id="app">
<h1>Vue 2 + @vue/composition-api + VueUse 示例</h1>
<UseMouseExample />
<UseLocalStorageExample />
<UseNetworkExample />
<UseClipboardExample />
<UseDebounceThrottleExample />
<UseTimeExample />
<UseFullscreenExample />
<!-- 你可以添加更多来自 @vueuse/core 的函数示例 -->
</div>
</template>
<script>
import UseMouseExample from './components/UseMouseExample.vue';
import UseLocalStorageExample from './components/UseLocalStorageExample.vue';
import UseNetworkExample from './components/UseNetworkExample.vue';
import UseClipboardExample from './components/UseClipboardExample.vue';
import UseDebounceThrottleExample from './components/UseDebounceThrottleExample.vue';
import UseTimeExample from './components/UseTimeExample.vue';
import UseFullscreenExample from './components/UseFullscreenExample.vue';
export default {
name: 'App',
components: {
UseMouseExample,
UseLocalStorageExample,
UseNetworkExample,
UseClipboardExample,
UseDebounceThrottleExample,
UseTimeExample,
UseFullscreenExample,
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: 20px;
}
h1, h3 {
margin-bottom: 15px;
}
</style>
注意事项与总结
@vue/composition-api
是关键: 没有这个插件,Vue 2 无法理解setup()
和其他 Composition API 功能,也就无法使用 VueUse。务必正确安装和注册。setup()
函数: 所有的 VueUse 函数调用都应该在setup()
函数内部进行。- 响应式: VueUse 函数通常返回响应式的数据(Refs 或 Reactive Objects)。你需要将它们从
setup
return 出去,才能在模板中使用。修改 Ref 时记得使用.value
。 - DOM Refs: 某些 VueUse 函数(如
useMouseInElement
,useFullscreen
)需要引用 DOM 元素。你需要使用@vue/composition-api
提供的ref(null)
创建一个 ref,在模板中通过ref="elementRefName"
绑定到元素上,并将这个 ref 从setup
返回。 - 生命周期: Composition API 有自己的生命周期钩子(如
onMounted
,onUnmounted
等),它们也由@vue/composition-api
提供。VueUse 内部通常会使用这些钩子来设置和清理事件监听器等。 - 兼容性: 虽然
@vue/composition-api
尽力模拟 Vue 3 的行为,但可能存在微小的差异或边缘情况。大多数@vueuse/core
函数在 Vue 2 中都能良好工作,因为它们主要依赖 Composition API 的核心功能。如果遇到问题,查阅 VueUse 和@vue/composition-api
的文档或 GitHub Issues 可能会有帮助。 - 按需引入: VueUse 支持摇树优化 (Tree Shaking),所以只引入你实际使用的函数,可以有效减小最终打包体积。