通过window.postMessage的history.push跳转后浏览器返回按钮异常问题
问题背景
- 网站包含React页面和Django模板渲染的iframe页面,通过
window.postMessage实现iframe到React页面的跳转 - Django侧发送消息代码:
window.parent.postMessage(JSON.stringify(postData), "https://" + main_domain);
- React侧监听消息并跳转的代码:
let { name } = useParams(); useEffect(() => { function handleMessage(e, originName) { if (originName === "from_iframe1") { props.history.push("next_url1"); } else if (originName === "from_iframe2") { props.history.push("next_url2"); } } if (name === "react_url1") { window.addEventListener( "message", e => handleMessage(e, "from_iframe1"), false ); } else if (name === "react_url2") { window.addEventListener( "message", e => handleMessage(e, "from_iframe2"), false ); } else { window.removeEventListener( "message", e => handleMessage(e, "others"), false ); } }, [name]);
- 异常表现:首次跳转后需点击2次返回按钮回退,后续依次需要4次、6次;改用
history.replaceState时首次跳转正常,但后续回退会跳过多个页面
核心原因
当前代码存在两个关键问题:
- 事件监听无法正确移除:每次
name变化时注册的是匿名箭头函数,移除时用的是另一个新的匿名函数,两者引用不同,导致旧的监听一直存在,同一个message事件会被多次触发,每次触发都执行一次history.push,历史栈中堆积大量重复记录 - 监听逻辑冗余:针对不同
name注册不同的监听函数,导致监听函数数量随页面切换不断增加
可行解决方案
方案1:修复事件监听的添加与移除逻辑
将监听函数提取为独立引用,确保添加和移除的是同一个函数,同时统一监听逻辑:
let { name } = useParams(); useEffect(() => { // 定义统一的监听函数,内部判断消息来源和当前页面 function handleMessage(e) { // 必须验证消息来源合法性,防止跨域恶意攻击 if (e.origin !== "https://" + main_domain) return; const data = JSON.parse(e.data); const originName = data.originName; // 假设postData包含标识来源的字段 if (name === "react_url1" && originName === "from_iframe1") { props.history.push("next_url1"); } else if (name === "react_url2" && originName === "from_iframe2") { props.history.push("next_url2"); } } window.addEventListener("message", handleMessage, false); // 组件卸载或name变化时自动移除监听 return () => { window.removeEventListener("message", handleMessage, false); }; }, [name, props.history, main_domain]);
方案2:限制跳转操作的执行频率
用标记位防止重复触发跳转,避免同一消息导致多次history.push:
let { name } = useParams(); const [isNavigating, setIsNavigating] = useState(false); useEffect(() => { function handleMessage(e) { if (e.origin !== "https://" + main_domain) return; if (isNavigating) return; // 正在跳转时忽略后续重复消息 const data = JSON.parse(e.data); const originName = data.originName; setIsNavigating(true); if (originName === "from_iframe1") { props.history.push("next_url1"); } else if (originName === "from_iframe2") { props.history.push("next_url2"); } // 延迟重置标记,避免短时间内重复跳转 setTimeout(() => setIsNavigating(false), 500); } window.addEventListener("message", handleMessage, false); return () => window.removeEventListener("message", handleMessage, false); }, [name, props.history, main_domain, isNavigating]);
方案3:优化history跳转逻辑
跳转前检查当前路径是否已经是目标路径,避免重复添加历史记录:
function handleMessage(e) { if (e.origin !== "https://" + main_domain) return; const data = JSON.parse(e.data); const originName = data.originName; let targetUrl = ""; if (originName === "from_iframe1") { targetUrl = "next_url1"; } else if (originName === "from_iframe2") { targetUrl = "next_url2"; } // 仅当当前路径不等于目标路径时才执行跳转 if (window.location.pathname !== `/${targetUrl}`) { props.history.push(targetUrl); } }
方案4:检查iframe侧的消息发送逻辑
确认Django侧的postMessage是否被多次调用(比如按钮绑定了多次点击事件、页面渲染时重复执行),导致React侧收到多条重复消息。可以添加标记位确保只发送一次消息:
// Django模板中添加防重复标记 let hasSentJumpMessage = false; function sendJumpMessage() { if (hasSentJumpMessage) return; hasSentJumpMessage = true; window.parent.postMessage(JSON.stringify(postData), "https://" + main_domain); } // 按钮点击时调用sendJumpMessage,而非直接执行postMessage
内容的提问来源于stack exchange,提问作者Anushka Chauhan
相关产品推荐
相关产品推荐

