如何在React单路由中加载Botpress v12第三方聊天脚本?
解决Botpress v12聊天机器人在React多路由中全局显示的问题
我在React项目里集成了Botpress v12的第三方聊天机器人,封装成BotpressChatbot组件后只想让它在单个路由页面显示,但现在所有路由都能看到——估计是因为脚本直接挂到了body上。
先贴我的原组件代码:
const BotpressChatbot = () => { useEffect(() => { const script = document.createElement('script'); const user = { user_profile: getUserFromSession()?.profile.name, oidc_User: sessionStorage.getItem( `${SESSION_OIDC_TOKEN_KEY}:${AUTHORITY}:${CLIENT}`, ), }; var my_data = JSON.stringify(user); script.src = 'https://dev.bot.ask.speciphic.ai/assets/modules/channel-web/inject.js'; script.addEventListener('load', () => { try { let config = { host: 'https://dev.bot.ask.speciphic.ai', botId: 'jllask', exposeStore: false, startNewConvoOnTimeout: true, useSessionStorage: true, enableTranscriptDownload: false, composerPlaceholder: 'Reply to JLL Ask', enableConversationDeletion: true, disableNotificationSound: false, extraStylesheet: 'https://dev.bot.ask.speciphic.ai/assets/modules/channel-web/jllAsk.css', }; window.botpressWebChat.init(config); window.addEventListener('message', function (event) { if (event.data.name && event.data.name == 'webchatReady') { window.botpressWebChat.sendEvent({ type: 'proactive-trigger', channel: 'web', payload: { text: my_data }, }); } else if ( event.data.payload && event.data.payload.type == 'session_reset' ) { window.botpressWebChat.sendEvent({ type: 'proactive-trigger', channel: 'web', payload: { text: my_data }, }); } }); } catch (e) { console.log('error in Botpress.jsx', e); } }); document.body.appendChild(script); return () => { document.body.removeChild(script); }; }, []); return <>></>; }; export default BotpressChatbot;
原useEffect的清理函数只删了自己创建的脚本标签,但Botpress初始化后会在body里生成一个ID为bp-web-widget的独立DOM容器,所以切换路由时聊天窗口根本没被删掉,导致全局都显示。
后来我把清理函数改成这样:
return () => { const widget = document.getElementById('bp-web-widget'); if (widget) document.body.removeChild(widget); };
这下切换路由能把聊天窗口删掉,回到目标页面也能重新渲染,但新问题来了——父页面因为有很多useEffect频繁触发重渲染,导致聊天机器人的脚本被重复加到body里。
解决办法:控制脚本仅加载一次+完善清理逻辑
核心思路是:
- 给脚本加全局标记,避免重复注入
- 清理时不仅删DOM,还要移除全局事件监听,避免内存泄漏
修改后的完整组件代码:
const BotpressChatbot = () => { useEffect(() => { // 先检查脚本是否已经加载过,避免重复注入 if (window.botpressWebChatLoaded) { // 如果已加载,检查聊天窗口是否存在,不存在就重新初始化 const widget = document.getElementById('bp-web-widget'); if (!widget) { initBotpressChat(); } return; } const script = document.createElement('script'); const user = { user_profile: getUserFromSession()?.profile.name, oidc_User: sessionStorage.getItem( `${SESSION_OIDC_TOKEN_KEY}:${AUTHORITY}:${CLIENT}`, ), }; const my_data = JSON.stringify(user); // 把初始化逻辑抽成函数,方便复用 function initBotpressChat() { try { const config = { host: 'https://dev.bot.ask.speciphic.ai', botId: 'jllask', exposeStore: false, startNewConvoOnTimeout: true, useSessionStorage: true, enableTranscriptDownload: false, composerPlaceholder: 'Reply to JLL Ask', enableConversationDeletion: true, disableNotificationSound: false, extraStylesheet: 'https://dev.bot.ask.speciphic.ai/assets/modules/channel-web/jllAsk.css', }; window.botpressWebChat.init(config); // 把消息监听函数存起来,方便后续清理 const messageListener = function (event) { if (event.data.name && event.data.name === 'webchatReady') { window.botpressWebChat.sendEvent({ type: 'proactive-trigger', channel: 'web', payload: { text: my_data }, }); } else if ( event.data.payload && event.data.payload.type === 'session_reset' ) { window.botpressWebChat.sendEvent({ type: 'proactive-trigger', channel: 'web', payload: { text: my_data }, }); } }; window.addEventListener('message', messageListener); window.botpressMessageListener = messageListener; } catch (e) { console.log('error in Botpress.jsx', e); } } script.src = 'https://dev.bot.ask.speciphic.ai/assets/modules/channel-web/inject.js'; script.addEventListener('load', () => { // 标记脚本已加载 window.botpressWebChatLoaded = true; initBotpressChat(); }); document.body.appendChild(script); // 完善清理函数 return () => { // 删除聊天窗口DOM const widget = document.getElementById('bp-web-widget'); if (widget) { document.body.removeChild(widget); } // 移除全局消息监听 if (window.botpressMessageListener) { window.removeEventListener('message', window.botpressMessageListener); delete window.botpressMessageListener; } // 移除脚本标签(可选,脚本加载后其实就没用了,想彻底清理就加上) if (script && script.parentNode) { script.parentNode.removeChild(script); } // 如果希望离开页面后再次进入重新加载脚本,可以删掉下面的注释 // delete window.botpressWebChatLoaded; }; }, []); return <>></>; }; export default BotpressChatbot;
额外优化:绑定路由变化(可选)
如果父页面重渲染频繁但路由没变化,可以用react-router的useLocation监听路由,只在目标路由时初始化聊天机器人:
import { useLocation } from 'react-router-dom'; const BotpressChatbot = () => { const location = useLocation(); useEffect(() => { // 替换成你的目标路由路径 const isTargetRoute = location.pathname === '/your-target-page'; if (!isTargetRoute) { // 非目标路由时直接清理聊天窗口和监听事件 const widget = document.getElementById('bp-web-widget'); if (widget) { document.body.removeChild(widget); } if (window.botpressMessageListener) { window.removeEventListener('message', window.botpressMessageListener); delete window.botpressMessageListener; } return; } // 下面放之前的初始化逻辑... }, [location.pathname]); return <>></>; };
内容的提问来源于stack exchange,提问作者Jayanta Basumatary
相关产品推荐
相关产品推荐

