React中如何阻止用户在多个浏览器标签页点击同一UI元素
实现方案
优先采用localStorage+storage事件的实现方案,该方案兼容Chrome 4+、Firefox 3.5+、Safari 4+、Edge 12+、IE8+所有主流浏览器,无需额外做兼容性降级,核心逻辑通过跨标签页共享存储状态实现会话独占锁定。
完整修改代码
你只需要替换原有的ChatBot组件代码即可:
import { useState, useEffect } from 'react'; import React from 'react'; // 固定存储key,避免和其他业务逻辑冲突 const CHAT_SESSION_LOCK_KEY = 'active_chat_session_tab'; // 生成当前标签页唯一标识 const CURRENT_TAB_ID = Math.random().toString(36).slice(2); export default function ChatBot() { const [showIcon, setShowIcon] = useState(true); const [isSessionOccupied, setIsSessionOccupied] = useState(false); useEffect(() => { // 检查当前是否有其他标签页占用会话 const checkSessionStatus = () => { const activeTab = localStorage.getItem(CHAT_SESSION_LOCK_KEY); setIsSessionOccupied(!!activeTab && activeTab !== CURRENT_TAB_ID); }; // 组件挂载先检查一次状态 checkSessionStatus(); // 监听其他标签页的存储变更,实时更新状态 const handleStorageUpdate = (e) => { if (e.key === CHAT_SESSION_LOCK_KEY) { checkSessionStatus(); } }; window.addEventListener('storage', handleStorageUpdate); // 标签页关闭时自动释放自己持有的锁 const handleTabClose = () => { const activeTab = localStorage.getItem(CHAT_SESSION_LOCK_KEY); if (activeTab === CURRENT_TAB_ID) { localStorage.removeItem(CHAT_SESSION_LOCK_KEY); } }; window.addEventListener('beforeunload', handleTabClose); // 组件卸载清理监听事件 return () => { window.removeEventListener('storage', handleStorageUpdate); window.removeEventListener('beforeunload', handleTabClose); }; }, []); const addChatScript = () => { // 会话被占用时直接拦截点击 if (isSessionOccupied) return; // 锁定会话 localStorage.setItem(CHAT_SESSION_LOCK_KEY, CURRENT_TAB_ID); setShowIcon(false); const srcUrl = 'https://otherwebsite.com/chat.js'; const scriptTag = document.createElement('script'); scriptTag.id = 'chatbot'; scriptTag.src = srcUrl; scriptTag.type = 'text/javascript'; // 脚本加载失败自动释放锁,避免永久锁死 scriptTag.onerror = () => { localStorage.removeItem(CHAT_SESSION_LOCK_KEY); setShowIcon(true); }; document.body.appendChild(scriptTag); }; return ( <div className="bot" style={{ position: 'fixed', right: '20px', bottom: '20px', cursor: isSessionOccupied ? 'not-allowed' : 'pointer', }} > {showIcon && ( <img src="https://images.ctfassets.net/hrltx12pl8hq/3MbF54EhWUhsXunc5Keueb/60774fbbff86e6bf6776f1e17a8016b4/04-nature_721703848.jpg?fit=fill&w=480&h=270" onClick={addChatScript} alt="聊天机器人" title={isSessionOccupied ? "您在其他标签页已有会话在运行" : ""} /> )} </div> ); }
可选异常优化
如果需要处理浏览器崩溃、强制退出导致锁未释放的极端场景,可以给锁增加过期时间,修改检查逻辑和加锁逻辑即可:
// 检查逻辑增加过期判断 const checkSessionStatus = () => { const lockValue = localStorage.getItem(CHAT_SESSION_LOCK_KEY); if (!lockValue) { setIsSessionOccupied(false); return; } try { const { tabId, timestamp } = JSON.parse(lockValue); // 锁有效期设置为24小时,可自定义 const isExpired = Date.now() - timestamp > 24 * 60 * 60 * 1000; if (isExpired || tabId === CURRENT_TAB_ID) { setIsSessionOccupied(false); isExpired && localStorage.removeItem(CHAT_SESSION_LOCK_KEY); } else { setIsSessionOccupied(true); } } catch (err) { // 兼容旧格式数据,直接清理 localStorage.removeItem(CHAT_SESSION_LOCK_KEY); setIsSessionOccupied(false); } }; // 加锁逻辑修改为存储带时间戳的对象 localStorage.setItem(CHAT_SESSION_LOCK_KEY, JSON.stringify({ tabId: CURRENT_TAB_ID, timestamp: Date.now() }));
内容的提问来源于stack exchange,提问作者coderX
相关产品推荐
相关产品推荐

