Video by Abbat Studio on Pexels
2978 字
15 分钟
Vue 开发中常用的工具函数 强烈建议收藏!
本文同步自 CSDN · 清阿哥,原文发布于 2025-03-20。
所有的工具函数使用背景都是 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 个 后续继续更新第二篇 第三篇…敬请期待~ 如果感觉有所帮助 欢迎点赞收藏推荐 如有不恰当的地方或者更好的建议 也欢迎留言交流~~ 感谢阅读 再见~
Vue 开发中常用的工具函数 强烈建议收藏!
https://qingge-cabana.vercel.app/posts/vue-开发中常用的工具函数-强烈建议收藏-146388587/ 
评论 待开启
评论基于 GitHub Discussions(Giscus)。仓库推送并开启 Discussions 后,在 giscus.app 获取
repoId/categoryId,填入src/config.ts的giscusConfig即可。HHQ-666/qingge-blog