在 vue 中优雅封装 resize 事件[单例模式、订阅机制、防抖...]

367 阅读4分钟

前言

最近在做 大屏可视化

  • 需求:监听屏幕尺寸变化对echarts进行重绘,确保数据可视化组件随窗口大小自动调整

  • 亮点1: 使用单例模式,整个应用只会有一个窗口大小监听器,避免了性能浪费

  • 亮点2: resize 事件使用 200ms 防抖处理,避免频繁触发导致的性能问题

  • 亮点3: 提供事件订阅机制让多个组件可以同时监听屏幕变化。

工具函数封装在 @/utils/screenManager.js

源码

// utils/screenManager.js
import { ref, provide, inject } from "vue";
import debounce from "lodash/debounce";

/**
 * 单例模式的屏幕尺寸管理器,用于监听窗口大小变化并通知订阅者
 */
let screenManagerInstance = null;
// 定义屏幕变化事件名称常量
export const SCREEN_CHANGE_EVENT = "screen-change";

/**
 * 屏幕管理器类,负责监听窗口大小变化并维护订阅者回调
 */
export class ScreenManager {
  constructor() {
    // 实现单例模式,确保全局只有一个实例
    if (screenManagerInstance) return screenManagerInstance;

    // 使用Vue的ref响应式对象存储屏幕尺寸
    this.screenWidth = ref(window.innerWidth);
    this.screenHeight = ref(window.innerHeight);

    // 使用Set数据结构存储事件监听器,确保唯一性
    this.eventListeners = new Set();

    // 使用lodash的debounce函数优化resize事件处理,每200ms执行一次
    // 绑定this指向,确保回调函数中this指向当前实例
    this.handleResize = debounce(this.handleResize.bind(this), 200);

    // 立即注册窗口大小变化事件
    window.addEventListener("resize", this.handleResize);

    // 保存当前实例为单例
    screenManagerInstance = this;
  }

  /**
   * 窗口大小变化的处理函数
   * 会更新存储的宽高值并通知所有订阅者
   */
  handleResize() {
    const width = window.innerWidth;
    const height = window.innerHeight;

    // 只有当宽高值真正发生变化时才更新并触发回调
    if (
      width !== this.screenWidth.value ||
      height !== this.screenHeight.value
    ) {
      this.screenWidth.value = width;
      this.screenHeight.value = height;

      // 遍历所有注册的监听器并传递新的宽高值
      this.eventListeners.forEach((callback) => {
        callback({ width, height });
      });
    }
  }

  /**
   * 注册事件监听器
   * @param {string} event - 事件名称,使用SCREEN_CHANGE_EVENT常量
   * @param {Function} callback - 屏幕变化时调用的回调函数
   */
  on(event, callback) {
    if (event === SCREEN_CHANGE_EVENT) {
      this.eventListeners.add(callback);
    }
  }

  /**
   * 移除事件监听器
   * @param {string} event - 事件名称,使用SCREEN_CHANGE_EVENT常量
   * @param {Function} callback - 要移除的回调函数引用
   */
  off(event, callback) {
    if (event === SCREEN_CHANGE_EVENT) {
      this.eventListeners.delete(callback);
    }
  }

  /**
   * 销毁管理器实例,清理事件监听器
   * 应在应用卸载时调用以避免内存泄漏
   */
  destroy() {
    window.removeEventListener("resize", this.handleResize);
    this.eventListeners.clear();
    screenManagerInstance = null;
  }
}

// 立即创建屏幕管理器实例,确保应用启动时就开始监听
export const screenManager = new ScreenManager();

/**
 * Vue插件实现,用于在应用中全局提供屏幕管理器
 */
export const ScreenManagerPlugin = {
  install(app) {
    // 通过Vue的provide/inject机制提供实例
    app.provide("screenManager", screenManager);
  },
};

/**
 * 组合式API,用于在组件中获取屏幕管理器实例
 * @returns {ScreenManager} 屏幕管理器实例
 */
export function useScreenManager() {
  // 优先从Vue的inject获取,否则使用全局实例
  return inject("screenManager") || screenManager;
}

使用文档

一、安装依赖与配置

项目使用到了lodash处理防抖

  1. 安装依赖

    npm install lodash  # 如果项目中尚未安装,用这个
    
  2. 注册插件: 在 main.js中注册插件:

    // ....省略部分内容
    // -------- 关键注册 --------
    import { ScreenManagerPlugin } from "@/utils/screenManager";
    
    // const app = createApp(App);
    
    app.use(ScreenManagerPlugin); // 注册屏幕管理插件
    
    // app.mount("#app");
    

三、使用方式

ScreenManager 提供了两种主要的使用方式:

方式一:使用 useScreenManager 组合式函数

其实是直接watch宽高数值

<template>
  <div>
    <p>当前屏幕宽度: {{ screenWidth }}</p>
    <p>当前屏幕高度: {{ screenHeight }}</p>
  </div>
</template>

<script setup>
import { ref, watch } from "vue";
import { useScreenManager } from "@/utils/screenManager";

const screenManager = useScreenManager();
const screenWidth = ref(screenManager.screenWidth.value);
const screenHeight = ref(screenManager.screenHeight.value);

// 使用watch监听屏幕尺寸变化
watch(
  () => [screenManager.screenWidth.value, screenManager.screenHeight.value],
  ([width, height]) => {
    screenWidth.value = width;
    screenHeight.value = height;
    console.log(`屏幕尺寸已变更为: ${width} x ${height}`);

    // 在这里可以添加响应式逻辑
    if (width < 768) {
      console.log("当前处于移动设备视图");
    }
  },
  { immediate: true } // 立即执行一次回调
);
</script>

方式二:通过.on/.off事件监听

<template>
  <div>
    <p>当前屏幕宽度: {{ screenWidth }}</p>
    <p>当前屏幕高度: {{ screenHeight }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { useScreenManager, SCREEN_CHANGE_EVENT } from "@/utils/screenManager";

const screenManager = useScreenManager();
const screenWidth = ref(window.innerWidth);
const screenHeight = ref(window.innerHeight);

// 定义屏幕变化的回调函数
const handleScreenChange = ({ width, height }) => {
  screenWidth.value = width;
  screenHeight.value = height;
  console.log(`屏幕尺寸变更为: ${width} x ${height}`);

  // 可添加自定义响应式逻辑
  if (width < 768) {
    // 执行移动端适配逻辑
  }
};

// 生命周期钩子:组件挂载时注册事件监听
onMounted(() => {
  screenManager.on(SCREEN_CHANGE_EVENT, handleScreenChange);
});

// 生命周期钩子:组件卸载时移除事件监听,防止内存泄漏
onUnmounted(() => {
  screenManager.off(SCREEN_CHANGE_EVENT, handleScreenChange);
});
</script>