记录老项目Vue 2使用VueUse

由于目前维护的项目比较老,所以想记录下在 Vue 2 项目中使用 VueUse (@vueuse/core) 这个强大的组合式函数库, 写下这篇文章。

VueUse 主要是为 Vue 3 的 Composition API 设计的,但幸运的是,Vue 官方提供了一个插件 @vue/composition-api,它将 Composition API 的核心功能引入到了 Vue 2.x 中。这使得我们可以在 Vue 2 项目里享受到 VueUse 带来的便利。

核心步骤:

  1. 安装依赖: 安装 @vue/composition-api@vueuse/core
  2. 注册插件: 在你的 Vue 应用入口文件 (main.jsmain.ts) 中注册 @vue/composition-api 插件。
  3. 使用 Composition API: 在 Vue 组件中使用 setup() 函数来组织逻辑。
  4. 引入和使用 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 函数在 beforeCreatecreated 生命周期钩子之前执行。它接收 propscontext (包含 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: useDebounceFnuseThrottleFn - 函数防抖与节流

这两个函数用于控制函数的执行频率,常用于性能优化,如处理输入事件、窗口大小调整等。

  • 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: useDateFormatuseNow - 时间格式化与实时时间

  • 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>

注意事项与总结

  1. @vue/composition-api 是关键: 没有这个插件,Vue 2 无法理解 setup() 和其他 Composition API 功能,也就无法使用 VueUse。务必正确安装和注册。
  2. setup() 函数: 所有的 VueUse 函数调用都应该在 setup() 函数内部进行。
  3. 响应式: VueUse 函数通常返回响应式的数据(Refs 或 Reactive Objects)。你需要将它们从 setup return 出去,才能在模板中使用。修改 Ref 时记得使用 .value
  4. DOM Refs: 某些 VueUse 函数(如 useMouseInElement, useFullscreen)需要引用 DOM 元素。你需要使用 @vue/composition-api 提供的 ref(null) 创建一个 ref,在模板中通过 ref="elementRefName" 绑定到元素上,并将这个 ref 从 setup 返回。
  5. 生命周期: Composition API 有自己的生命周期钩子(如 onMounted, onUnmounted 等),它们也由 @vue/composition-api 提供。VueUse 内部通常会使用这些钩子来设置和清理事件监听器等。
  6. 兼容性: 虽然 @vue/composition-api 尽力模拟 Vue 3 的行为,但可能存在微小的差异或边缘情况。大多数 @vueuse/core 函数在 Vue 2 中都能良好工作,因为它们主要依赖 Composition API 的核心功能。如果遇到问题,查阅 VueUse 和 @vue/composition-api 的文档或 GitHub Issues 可能会有帮助。
  7. 按需引入: VueUse 支持摇树优化 (Tree Shaking),所以只引入你实际使用的函数,可以有效减小最终打包体积。
相关推荐
恋猫de小郭30 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端