React JS中添加仅在应用内生效的自定义返回按钮求助
解决React应用返回按钮避免跳转到外部页面的问题
这个问题确实很常见——谁都不想用户不小心从自己的React应用点返回就跳到外部网站对吧?我有几个可行的方案,你可以根据自己的技术栈来选择:
方案一:基于React Router维护应用内历史栈(推荐)
如果你的项目用了React Router(现在主流是v6版本),我们可以自己维护一个应用内的路由历史数组,通过这个数组的长度来判断是否还有应用内的上一页可以返回。
实现代码
import { useState, useEffect } from 'react'; import { useLocation, useNavigate, Outlet } from 'react-router-dom'; function App() { const location = useLocation(); const navigate = useNavigate(); // 初始化时把当前路由加入应用内历史栈 const [appHistory, setAppHistory] = useState([location.pathname]); // 监听应用内路由变化,更新历史栈(排除同页面重复路由的情况) useEffect(() => { const lastPath = appHistory[appHistory.length - 1]; if (lastPath !== location.pathname) { setAppHistory(prev => [...prev, location.pathname]); } }, [location.pathname]); // 监听浏览器的popstate事件,同步更新应用内历史栈(处理用户点击浏览器自带返回按钮的情况) useEffect(() => { const handlePopState = () => { setAppHistory(prev => { return prev.length > 1 ? prev.slice(0, -1) : prev; }); }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, []); // 判断是否可以返回:应用内历史栈长度大于1,说明还有应用内的上一页 const canGoBack = appHistory.length > 1; const handleGoBack = () => { if (canGoBack) { navigate(-1); } }; return ( <div className="app-container"> {/* 根据canGoBack状态决定显示/禁用返回按钮 */} <button onClick={handleGoBack} disabled={!canGoBack} style={{opacity: canGoBack ? 1 : 0.5, cursor: canGoBack ? 'pointer' : 'not-allowed'}} > 返回上一页 </button> {/* 渲染子路由组件 */} <Outlet /> </div> ); } export default App;
方案说明
- 我们通过
useLocation监听应用内的路由变化,把每个合法的路由路径加入appHistory数组 - 同时监听浏览器的
popstate事件,当用户点击浏览器自带的返回按钮时,同步缩短我们的应用内历史栈 - 当
appHistory长度大于1时,说明还有应用内的页面可以返回;否则就禁用或隐藏按钮,避免跳出应用
方案二:原生History API追踪历史长度(无React Router场景)
如果你的项目没有用React Router,直接用原生的History API来导航,我们可以通过记录初始进入应用时的浏览器历史长度,来判断当前是否还在应用内的历史栈中。
实现代码
import { useState, useEffect } from 'react'; function BackButton() { const [initialHistoryLen, setInitialHistoryLen] = useState(null); const [currentHistoryLen, setCurrentHistoryLen] = useState(null); useEffect(() => { // 初始化时记录当前浏览器历史的总长度 const initialLen = window.history.length; setInitialHistoryLen(initialLen); setCurrentHistoryLen(initialLen); // 监听popstate事件,更新当前历史长度 const handlePopState = () => { setCurrentHistoryLen(window.history.length); }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, []); // 封装应用内的导航函数,确保每次导航都更新历史长度 const navigateTo = (path) => { window.history.pushState({}, '', path); setCurrentHistoryLen(prev => prev + 1); // 这里可以根据需要触发页面内容更新,比如发布自定义事件或者更新相关状态 }; // 判断逻辑:当前历史长度大于初始长度,说明还有应用内的页面可以返回 const canGoBack = currentHistoryLen > initialHistoryLen; const handleGoBack = () => { if (canGoBack) { window.history.back(); } }; return ( <button onClick={handleGoBack} disabled={!canGoBack} style={{opacity: canGoBack ? 1 : 0.5}} > 返回 </button> ); } export default BackButton;
方案说明
- 初始进入应用时,记录下浏览器历史的总长度
initialHistoryLen - 每次应用内用
pushState导航时,手动增加currentHistoryLen - 当
currentHistoryLen大于initialHistoryLen时,说明我们还在应用内的历史栈中,点击返回只会跳转到应用内的上一页;否则就会跳出应用,这时候禁用按钮
注意事项
- 刷新页面的情况:如果用户在应用内刷新页面,
initialHistoryLen会被重置为当前浏览器历史长度,这时候判断逻辑会认为没有应用内的上一页,这其实是合理的——因为刷新后浏览器历史里的当前页面是新条目,上一页确实是外部页面(或者应用的首页,但刷新后无法追踪之前的应用内历史) document.referrer的局限性:虽然可以用document.referrer判断进入应用的第一个页面的上一页是否同源,但这个值可能为空(比如用户直接输入URL进入应用),而且无法追踪应用内的多页面导航,所以只能作为辅助判断,不能单独依赖
内容的提问来源于stack exchange,提问作者LukeJUKE
相关产品推荐
相关产品推荐

