Vue 开发中常用的工具函数 强烈建议收藏!

所有的工具函数使用背景都是 vue 框架(vue2 vue3)+ ts 项目中使用部分需要自行调整

1. 防抖函数

使用场景:按钮点击(请求接口) 输入框输入(模糊搜索)计算鼠标的位置
之前有写过一篇关于 vue 中使用节流防抖

/**
 * 防抖函数
 * @param fn 回调函数
 * @param timeout 延迟时间
 * @param immediate 是否立即执行
 */
export const debounce = <T extends FunctionArgs>(
  fn: T,
  timeout: MaybeRef<number> = 200,
  immediate = false
) => {
  let timmer: TimeoutHandle;
  const wait = unref(timeout);
  return () => {
    timmer && clearTimeout(timmer);
    if (immediate) {
      if (!timmer) {
        fn();
      }
      timmer = setTimeout(() => (timmer = null), wait);
    } else {
      timmer = setTimeout(fn, wait);
    }
  };
};

2. 节流函数

/**
 *
 * @param fn 回调函数
 * @param timeout 延迟时间
 */
export const throttle = (fn, timeout = 200) => {
  let lastExecutedTime = 0;
  return function (...args) {
    const currentTime = Date.now();
    if (currentTime - lastExecutedTime > timeout) {
      fn.apply(this, args);
      lastExecutedTime = currentTime;
    }
  };
};

3. 检查当前设备类型

/**
 * 检测当前设备是否为移动设备
 * 
 * 通过检查用户代理(User Agent)字符串判断当前设备类型
 * 
 * @returns {boolean} 如果是移动设备返回true,桌面设备返回false
 * 
 * @example
 * if (deviceDetection()) {
 *   // 移动设备逻辑
 * } else {
 *   // 桌面设备逻辑
 * }
 */
export const deviceDetection = (): boolean => {
  const userAgent: string = navigator.userAgent.toLowerCase();

  // 定义移动设备的特征标识
  const mobileIdentifiers = [
    /iphone os/i,     // iOS设备
    /midp/i,          // 早期Java手机平台
    /rv:1.2.3.4/i,    // 特定UC浏览器版本
    /ucweb/i,         // UC浏览器
    /android/i,       // 安卓设备
    /windows ce/i,    // Windows CE设备
    /windows mobile/i // Windows Mobile设备
  ];

  // 检查是否匹配任一移动设备标识
  return mobileIdentifiers.some(pattern => pattern.test(userAgent));
};

## 3. 获取当前浏览器信息
```js

/**
 * 获取当前浏览器信息,包括浏览器类型和版本号
 * 
 * @returns {BrowserInter} 包含浏览器类型和版本的对象
 * @property {string} browser - 浏览器类型 (msie|firefox|chrome|opera|safari)
 * @property {string} version - 浏览器版本号
 * 
 * @example
 * const { browser, version } = getBrowserInfo();
 * console.log(`当前浏览器: ${browser} ${version}`);
 */
export const getBrowserInfo = (): BrowserInter => {
  const userAgent = navigator.userAgent.toLowerCase();
  const regExp = /(msie|firefox|chrome|opera|version).*?([\d.]+)/;
  const match = userAgent.match(regExp);

  if (!match || match.length < 3) {
    return {
      browser: 'unknown',
      version: '0.0'
    };
  }

  // 特殊处理Safari浏览器(Safari使用'version'而不是'safari'标识)
  const browser = match[1] === 'version' ? 'safari' : match[1];

  return {
    browser,
    version: match[2]
  };
};

4. js 操作 Class 类名相关

/**
 * 检查DOM元素是否包含指定的类名
 * 
 * @param {RefType<any>} ele - 要检查的DOM元素
 * @param {string} cls - 要检查的类名
 * @returns {boolean} 如果元素包含指定类名则返回true,否则返回false
 * 
 * @example
 * if (hasClass(element, 'active')) {
 *   // 元素包含'active'类的处理逻辑
 * }
 */
export const hasClass = (ele: RefType<any>, cls: string): boolean => {
  if (!ele || !ele.className || typeof ele.className !== 'string') return false;
  return new RegExp("(\\s|^)" + cls + "(\\s|$)").test(ele.className);
};

/**
 * 为DOM元素添加一个或两个类名
 * 
 * @param {RefType<any>} ele - 要添加类名的DOM元素
 * @param {string} cls - 要添加的主要类名
 * @param {string} [extracls] - 可选的额外类名
 * @returns {void}
 * 
 * @example
 * addClass(element, 'active');
 * addClass(element, 'primary', 'large');
 */
export const addClass = (
  ele: RefType<any>,
  cls: string,
  extracls?: string
): void => {
  if (!ele || !cls) return;

  if (!hasClass(ele, cls)) {
    ele.className = ele.className ? `${ele.className} ${cls}` : cls;
  }

  if (extracls && !hasClass(ele, extracls)) {
    ele.className = `${ele.className} ${extracls}`;
  }
};

/**
 * 从DOM元素移除一个或两个类名
 * 
 * @param {RefType<any>} ele - 要移除类名的DOM元素
 * @param {string} cls - 要移除的主要类名
 * @param {string} [extracls] - 可选的额外要移除的类名
 * @returns {void}
 * 
 * @example
 * removeClass(element, 'active');
 * removeClass(element, 'primary', 'large');
 */
export const removeClass = (
  ele: RefType<any>,
  cls: string,
  extracls?: string
): void => {
  if (!ele || !ele.className || !cls) return;

  if (hasClass(ele, cls)) {
    const reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
    ele.className = ele.className.replace(reg, " ").trim();
  }

  if (extracls && hasClass(ele, extracls)) {
    const regs = new RegExp("(\\s|^)" + extracls + "(\\s|$)");
    ele.className = ele.className.replace(regs, " ").trim();
  }
};

/**
 * 根据条件切换DOM元素的类名
 * 
 * @param {boolean} flag - 决定是否添加类名的标志,true添加,false移除
 * @param {string} clsName - 要切换的类名
 * @param {RefType<any>} [target] - 目标DOM元素,默认为document.body
 * @returns {void}
 * 
 * @example
 * // 根据darkMode变量切换body上的dark-theme类
 * toggleClass(darkMode, 'dark-theme');
 * // 切换特定元素的visible类
 * toggleClass(isVisible, 'visible', menuElement);
 */
export const toggleClass = (
  flag: boolean,
  clsName: string,
  target?: RefType<any>
): void => {
  if (!clsName) return;

  const targetEl = target || document.body;
  if (!targetEl) return;

  // 首先移除类名
  let { className } = targetEl;
  const reg = new RegExp("(\\s|^)" + clsName + "(\\s|$)");
  className = className.replace(reg, " ").trim();

  // 根据flag决定是否重新添加
  targetEl.className = flag ? `${className} ${clsName}` : className;
};

5. requestAnimationFrame 实现的节流函数

/**
 * 创建一个使用requestAnimationFrame实现的节流函数
 * 
 * 该函数确保回调在每个动画帧内最多执行一次,适用于滚动、调整大小等高频事件处理
 * 
 * @template T - 任意函数类型
 * @param {T} fn - 需要节流的原始函数
 * @returns {T} 节流处理后的函数
 * 
 * @example
 * // 创建一个节流的滚动处理函数
 * const throttledScroll = useRafThrottle(() => {
 *   console.log(window.scrollY);
 * });
 * 
 * // 添加滚动事件监听
 * window.addEventListener('scroll', throttledScroll);
 */
import type { FunctionArgs } from "@vueuse/core";

export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
  let locked = false;

  // @ts-ignore
  return function (...args) {
    if (locked) return;

    locked = true;
    window.requestAnimationFrame(() => {
      fn.apply(this, args);
      locked = false;
    });
  };
}

6. 监听元素变化(大小)

/**
 * ResizeObserver的回调处理函数
 * 处理元素大小变化事件,并触发所有注册的监听函数
 * 
 * @param {ResizeObserverEntry[]} entries - ResizeObserver提供的条目数组
 * @private
 */
const resizeHandler = function (entries: ResizeObserverEntry[]): void {
  for (const entry of entries) {
    const listeners = entry.target.__resizeListeners__ || [];
    if (listeners.length) {
      listeners.forEach(fn => {
        fn();
      });
    }
  }
};

7. 为 DOM 元素添加/移除大小变化监听器


/**
 * 为DOM元素添加大小变化监听器
 * 
 * 使用ResizeObserver监听元素大小变化,当元素大小变化时触发回调函数
 * 
 * @param {HTMLElement} element - 要监听大小变化的DOM元素
 * @param {Function} fn - 元素大小变化时调用的回调函数
 * @returns {void}
 * 
 * @example
 * // 监听容器大小变化并更新图表
 * addResizeListener(chartContainer, () => {
 *   chart.resize();
 * });
 */
export const addResizeListener = function (element: any, fn: () => void): void {
  if (isServer || !element) return;

  // 初始化元素上的监听器数组
  if (!element.__resizeListeners__) {
    element.__resizeListeners__ = [];

    // 创建ResizeObserver实例并开始观察元素
    element.__ro__ = new ResizeObserver(resizeHandler);
    element.__ro__.observe(element);
  }

  // 避免重复添加相同的监听器
  if (element.__resizeListeners__.indexOf(fn) === -1) {
    element.__resizeListeners__.push(fn);
  }
};


/**
 * 移除DOM元素上的大小变化监听器
 * 
 * 如果移除后元素上没有其他监听器,则停止对该元素的观察
 * 
 * @param {HTMLElement} element - 要移除监听器的DOM元素
 * @param {Function} fn - 要移除的特定监听器函数
 * @returns {void}
 * 
 * @example
 * // 移除之前添加的监听器
 * removeResizeListener(chartContainer, updateChart);
 */
export const removeResizeListener = function (element: any, fn: () => void): void {
  if (isServer || !element || !element.__resizeListeners__) return;

  // 查找并移除特定监听器
  const index = element.__resizeListeners__.indexOf(fn);
  if (index !== -1) {
    element.__resizeListeners__.splice(index, 1);
  }

  // 如果没有剩余监听器,停止观察并清理
  if (!element.__resizeListeners__.length && element.__ro__) {
    element.__ro__.disconnect();
    element.__ro__ = null;
  }
};

8. 本地存储 sessionStorage localStorage 使用封装

/**
 * Web存储代理接口
 * 定义了存储操作的标准方法集
 */
interface ProxyStorage {
  /**
   * 获取存储项
   * @param {string} key - 存储项的键
   * @returns {any} 存储的值
   */
  getItem(key: string): any;

  /**
   * 设置存储项
   * @param {string} key - 存储项的键
   * @param {string} value - 要存储的值
   */
  setItem(key: string, value: string): void;

  /**
   * 移除存储项
   * @param {string} key - 要移除的存储项的键
   */
  removeItem(key: string): void;

  /**
   * 清除所有存储项
   */
  clear(): void;
}

/**
 * sessionStorage操作代理类
 * 提供对sessionStorage的操作封装,支持存取JSON对象
 */
class SessionStorageProxy implements ProxyStorage {
  /**
   * 被代理的存储对象
   * @protected
   */
  protected storage: ProxyStorage;

  /**
   * 构造函数
   * @param {ProxyStorage} storageModel - 被代理的存储对象
   */
  constructor(storageModel: ProxyStorage) {
    this.storage = storageModel;
  }

  /**
   * 存储数据,自动将对象转换为JSON字符串
   * @param {string} key - 存储项的键
   * @param {any} value - 要存储的值,可以是任意类型
   */
  public setItem(key: string, value: any): void {
    if (key === undefined || key === null || key === '') {
      throw new Error('Storage key cannot be empty');
    }

    try {
      const valueStr = typeof value === 'object'
        ? JSON.stringify(value)
        : String(value);
      this.storage.setItem(key, valueStr);
    } catch (error) {
      console.error('Failed to store item:', error);
    }
  }

  /**
   * 获取数据,自动将JSON字符串转换回对象
   * @param {string} key - 存储项的键
   * @returns {any} 解析后的存储值,如果项不存在或解析失败则返回null
   */
  public getItem(key: string): any {
    if (key === undefined || key === null || key === '') {
      return null;
    }

    try {
      const value = this.storage.getItem(key);
      if (value === null || value === undefined) {
        return null;
      }
      return JSON.parse(value);
    } catch (error) {
      console.warn(`Error parsing stored item ${key}:`, error);
      return this.storage.getItem(key);
    }
  }

  /**
   * 移除指定的存储项
   * @param {string} key - 要移除的存储项的键
   */
  public removeItem(key: string): void {
    if (key) {
      this.storage.removeItem(key);
    }
  }

  /**
   * 清除所有存储项
   */
  public clear(): void {
    this.storage.clear();
  }
}

/**
 * localStorage操作代理类
 * 提供对localStorage的操作封装,继承自SessionStorageProxy
 */
class LocalStorageProxy extends SessionStorageProxy implements ProxyStorage {
  /**
   * 构造函数
   * @param {ProxyStorage} localStorage - 被代理的localStorage对象
   */
  constructor(localStorage: ProxyStorage) {
    super(localStorage);
  }
}

/**
 * sessionStorage实例
 * 用于操作浏览器的会话存储
 * 页面会话结束时数据会被清除
 * 
 * @example
 * // 存储数据
 * storageSession.setItem('user', { name: 'John', age: 30 });
 * 
 * // 获取数据
 * const user = storageSession.getItem('user');
 * 
 * // 删除数据
 * storageSession.removeItem('user');
 */
export const storageSession = new SessionStorageProxy(sessionStorage);

/**
 * localStorage实例
 * 用于操作浏览器的本地存储
 * 数据永久保存,除非被显式清除
 * 
 * @example
 * // 存储数据
 * storageLocal.setItem('theme', 'dark');
 * 
 * // 获取数据
 * const theme = storageLocal.getItem('theme');
 * 
 * // 删除数据
 * storageLocal.removeItem('theme');
 */
export const storageLocal = new LocalStorageProxy(localStorage);

9. js-cookie 使用封装

import Cookies from "js-cookie";

import useUserStoreHook from "/@/store/modules/user";

/**
 * 认证令牌的Cookie键名
 * 用于存储和获取认证令牌
 */
const TokenKey = "Admin-Token";

/**
 * 获取当前用户的认证令牌
 * 
 * 从Cookie中读取存储的令牌
 * 
 * @returns {string | undefined} 用户认证令牌,如果未登录则返回undefined
 * 
 * @example
 * const token = getToken();
 * if (token) {
 *   // 用户已登录
 * } else {
 *   // 用户未登录,重定向到登录页
 * }
 */
export function getToken(): string | undefined {
  return Cookies.get(TokenKey);
}

/**
 * 设置用户认证令牌
 * 
 * 将认证令牌存储到Cookie中,同时更新Vuex存储的状态
 * 
 * @param {object} data - 包含令牌信息的对象
 * @param {string} data.token - 用户认证令牌
 * @returns {void}
 * 
 * @example
 * // 登录成功后设置令牌
 * setToken({ token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' });
 */
export function setToken(data: { token: string }): void {
  if (!data || typeof data !== 'object') {
    console.error('Invalid token data provided');
    return;
  }

  const { token: accessToken } = data;

  if (!accessToken) {
    console.error('No access token provided');
    return;
  }

  // store 里面保存 token 的方法 更新Vuex存储的令牌状态
  useUserStoreHook().SET_TOKEN(accessToken);

  // 存储令牌到Cookie
  // 注意:这里没有设置过期时间,使用默认的会话Cookie
  Cookies.set(TokenKey, accessToken);
}

/**
 * 移除用户认证令牌
 * 
 * 从Cookie中删除存储的令牌,通常在用户登出时调用
 * 
 * @returns {void}
 * 
 * @example
 * // 用户登出
 * removeToken();
 * router.push('/login');
 */
export function removeToken(): void {
  Cookies.remove(TokenKey);
}

10. 获取当前环境变量

/**
 * 环境变量工具模块
 * 用于访问和加载Vite应用的环境变量
 */

/**
 * 加载应用的环境变量
 * 
 * 从Vite的import.meta.env中获取当前应用的所有环境变量。
 * 环境变量通常在.env文件中定义,并在构建时由Vite处理。
 * 
 * @returns {ViteEnv} 包含所有环境变量的对象
 * 
 * @example
 * // 获取所有环境变量
 * const env = loadEnv();
 * console.log('当前模式:', env.MODE);
 * console.log('API基础URL:', env.VITE_API_BASE_URL);
 * 
 * // 也可以直接解构需要的环境变量
 * const { VITE_APP_TITLE, VITE_API_BASE_URL } = loadEnv();
 */
export const loadEnv = (): ViteEnv => {
  try {
    // 确保我们在浏览器环境中
    if (typeof import.meta.env === 'undefined') {
      console.warn('环境变量不可用,可能不在Vite环境中运行');
      return {} as ViteEnv;
    }

    return import.meta.env;
  } catch (error) {
    console.error('加载环境变量时发生错误:', error);
    return {} as ViteEnv;
  }
};

第一篇先更新 10 个 后续继续更新第二篇 第三篇…敬请期待~ 如果感觉有所帮助 欢迎点赞收藏推荐 如有不恰当的地方或者更好的建议 也欢迎留言交流~~ 感谢阅读 再见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值