Intercept/handle React-router 中浏览器的后退按钮?
Intercept/handle browser's back button in React-router?
我正在使用 Material-ui 的选项卡,这些选项卡是受控的,我将它们用于(React-router)链接,如下所示:
<Tab value={0} label="dashboard" containerElement={<Link to="/dashboard/home"/>}/>
<Tab value={1} label="users" containerElement={<Link to="/dashboard/users"/>} />
<Tab value={2} label="data" containerElement={<Link to="/dashboard/data"/>} />
如果我正在访问 dashboard/data 并且我单击浏览器的后退按钮
我(例如)转到 dashboard/users,但突出显示的选项卡仍保留在 dashboard/data(值=2)
我可以通过设置状态来改变,但是我不知道如何处理按下浏览器后退按钮的事件?
我找到了这个:
window.onpopstate = this.onBackButtonEvent;
但是每次状态改变时都会调用它(不仅在后退按钮事件上)
这是我最终的做法:
componentDidMount() {
this._isMounted = true;
window.onpopstate = ()=> {
if(this._isMounted) {
const { hash } = location;
if(hash.indexOf('home')>-1 && this.state.value!==0)
this.setState({value: 0})
if(hash.indexOf('users')>-1 && this.state.value!==1)
this.setState({value: 1})
if(hash.indexOf('data')>-1 && this.state.value!==2)
this.setState({value: 2})
}
}
}
感谢大家的帮助哈哈
这是一个有点老的问题,您可能已经有了答案,但是对于像我这样需要这个的人,我将留下这个答案。
使用 react-router 使工作变得如此简单:
import { browserHistory } from 'react-router';
componentDidMount() {
super.componentDidMount();
this.onScrollNearBottom(this.scrollToLoad);
this.backListener = browserHistory.listen(location => {
if (location.action === "POP") {
// Do your stuff
}
});
}
componentWillUnmount() {
super.componentWillUnmount();
// Unbind listener
this.backListener();
}
React Router API 的版本 3.x 有 a set of utilities you can use to expose a "Back" button event before the event registers with the browser's history. You must first wrap your component in the withRouter()
higher-order component。然后,您可以使用 setRouteLeaveHook()
函数,该函数接受具有有效 path
属性 和回调函数的任何 route
对象。
import {Component} from 'react';
import {withRouter} from 'react-router';
class Foo extends Component {
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
}
routerWillLeave(nextState) { // return false to block navigation, true to allow
if (nextState.action === 'POP') {
// handle "Back" button clicks here
}
}
}
export default withRouter(Foo);
我使用了 withrouter hoc 来获取 history prop 并且只写了一个 componentDidMount() 方法:
componentDidMount() {
if (this.props.history.action === "POP") {
// custom back button implementation
}
}
挂钩示例
const {history} = useRouter();
useEffect(() => {
return () => {
// && history.location.pathname === "any specific path")
if (history.action === "POP") {
history.replace(history.location.pathname, /* the new state */);
}
};
}, [history])
我不用history.listen因为不影响状态
const disposeListener = history.listen(navData => {
if (navData.pathname === "/props") {
navData.state = /* the new state */;
}
});
您可以使用 "withrouter" HOC 并使用 this.props.history.goBack
.
<Button onClick={this.props.history.goBack}>
BACK
</Button>
这取决于你在 React 中使用的 Router 类型。
如果您使用 react-router 中的 BrowserRouter(虽然在 react-router v4 中不可用),如上所述,您可以使用操作 'POP' 拦截浏览器后退按钮。
但是,如果您使用HashRouter 来推送路由,上述解决方案将不起作用。原因是当您单击浏览器后退按钮或从您的组件推送路由时,哈希路由器总是通过 'POP' 操作触发。您不能简单地用 window.popstate 或 history.listen 来区分这两个动作。
使用钩子可以检测后退和前进按钮
import { useHistory } from 'react-router-dom'
const [ locationKeys, setLocationKeys ] = useState([])
const history = useHistory()
useEffect(() => {
return history.listen(location => {
if (history.action === 'PUSH') {
setLocationKeys([ location.key ])
}
if (history.action === 'POP') {
if (locationKeys[1] === location.key) {
setLocationKeys(([ _, ...keys ]) => keys)
// Handle forward event
} else {
setLocationKeys((keys) => [ location.key, ...keys ])
// Handle back event
}
}
})
}, [ locationKeys, ])
这个问题的大部分答案要么使用过时版本的 React Router,依赖不太现代的 Class 组件,要么令人困惑;和 none 使用 Typescript,这是一种常见的组合。这是使用 Router v5、函数组件和 Typescript 的答案:
// use destructuring to access the history property of the ReactComponentProps type
function MyComponent( { history }: ReactComponentProps) {
// use useEffect to access lifecycle methods, as componentDidMount etc. are not available on function components.
useEffect(() => {
return () => {
if (history.action === "POP") {
// Code here will run when back button fires. Note that it's after the `return` for useEffect's callback; code before the return will fire after the page mounts, code after when it is about to unmount.
}
}
})
}
可以找到更完整的示例和解释 here。
用于在响应功能组件中对浏览器的按下发出警告。执行以下步骤
- 声明 isBackButtonClicked 并将其初始化为 false 并使用 setBackbuttonPress 函数保持状态。
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
- 在componentdidmount中,添加如下几行代码
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
定义 onBackButtonEvent 函数并根据您的要求编写逻辑。
const onBackButtonEvent = (e) => {
e.preventDefault();
if (!isBackButtonClicked) {
if (window.confirm("Do you want to go to Test Listing")) {
setBackbuttonPress(true)
props.history.go(listingpage)
} else {
window.history.pushState(null, null, window.location.pathname);
setBackbuttonPress(false)
}
}
}
在componentwillmount取消订阅onBackButtonEvent函数
最终代码将如下所示
import React,{useEffect,useState} from 'react'
function HandleBrowserBackButton() {
const [isBackButtonClicked, setBackbuttonPress] = useState(false)
useEffect(() => {
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
//logic for showing popup warning on page refresh
window.onbeforeunload = function () {
return "Data will be lost if you leave the page, are you sure?";
};
return () => {
window.removeEventListener('popstate', onBackButtonEvent);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onBackButtonEvent = (e) => {
e.preventDefault();
if (!isBackButtonClicked) {
if (window.confirm("Do you want to go to Test Listing")) {
setBackbuttonPress(true)
props.history.go(listingpage)
} else {
window.history.pushState(null, null, window.location.pathname);
setBackbuttonPress(false)
}
}
}
return (
<div>
</div>
)
}
export default HandleBrowserBackButton
即将推出的 6.0 版引入了 useBlocker 挂钩 - 可用于拦截所有导航尝试。
import { Action } from 'history';
import { useBlocker } from 'react-router';
// when blocker should be active
const unsavedChanges = true;
useBlocker((transition) => {
const {
location, // The new location
action, // The action that triggered the change
} = transition;
// intercept back and forward actions:
if (action === Action.Pop) {
alert('intercepted!')
}
}, unsavedChanges);
使用钩子。我已将@Nicolas Keller 的代码转换为 typescript
const [locationKeys, setLocationKeys] = useState<(string | undefined)[]>([]);
const history = useHistory();
useEffect(() => {
return history.listen((location) => {
if (history.action === 'PUSH') {
if (location.key) setLocationKeys([location.key]);
}
if (history.action === 'POP') {
if (locationKeys[1] === location.key) {
setLocationKeys(([_, ...keys]) => keys);
// Handle forward event
console.log('forward button');
} else {
setLocationKeys((keys) => [location.key, ...keys]);
// Handle back event
console.log('back button');
removeTask();
}
}
});
}, [locationKeys]);
如果你使用的是React Router V5,可以试试Prompt。
Used to prompt the user before navigating away from a page. When your application enters a state that should prevent the user from navigating away (like a form is half-filled out), render a <Prompt>
<Prompt
message={(location, action) => {
if (action === 'POP') {
console.log("Backing up...")
// Add your back logic here
}
return true;
}}
/>
只需放入 componentDidMount()
componentDidMount() {
window.onbeforeunload =this.beforeUnloadListener;
}
beforeUnloadListener = (event) => {
event.preventDefault();
return event.returnValue = "Are you sure you want to exit?";
};
将这两行添加到您的 componentDidMount() 中。这对我有用
window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', function(event) {
window.location.replace(
`YOUR URL`
);
});
在 NextJs 中,我们可以使用 beforePopState 函数并执行我们想要的操作,例如关闭模态或显示模态或检查后退地址并决定要做什么
const router = useRouter();
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as === '/' ) {
// Have SSR render bad routes as a 404.
window.location.href = as;
closeModal();
return false
}
return true
})
}, [])
我正在使用 Material-ui 的选项卡,这些选项卡是受控的,我将它们用于(React-router)链接,如下所示:
<Tab value={0} label="dashboard" containerElement={<Link to="/dashboard/home"/>}/>
<Tab value={1} label="users" containerElement={<Link to="/dashboard/users"/>} />
<Tab value={2} label="data" containerElement={<Link to="/dashboard/data"/>} />
如果我正在访问 dashboard/data 并且我单击浏览器的后退按钮 我(例如)转到 dashboard/users,但突出显示的选项卡仍保留在 dashboard/data(值=2)
我可以通过设置状态来改变,但是我不知道如何处理按下浏览器后退按钮的事件?
我找到了这个:
window.onpopstate = this.onBackButtonEvent;
但是每次状态改变时都会调用它(不仅在后退按钮事件上)
这是我最终的做法:
componentDidMount() {
this._isMounted = true;
window.onpopstate = ()=> {
if(this._isMounted) {
const { hash } = location;
if(hash.indexOf('home')>-1 && this.state.value!==0)
this.setState({value: 0})
if(hash.indexOf('users')>-1 && this.state.value!==1)
this.setState({value: 1})
if(hash.indexOf('data')>-1 && this.state.value!==2)
this.setState({value: 2})
}
}
}
感谢大家的帮助哈哈
这是一个有点老的问题,您可能已经有了答案,但是对于像我这样需要这个的人,我将留下这个答案。
使用 react-router 使工作变得如此简单:
import { browserHistory } from 'react-router';
componentDidMount() {
super.componentDidMount();
this.onScrollNearBottom(this.scrollToLoad);
this.backListener = browserHistory.listen(location => {
if (location.action === "POP") {
// Do your stuff
}
});
}
componentWillUnmount() {
super.componentWillUnmount();
// Unbind listener
this.backListener();
}
React Router API 的版本 3.x 有 a set of utilities you can use to expose a "Back" button event before the event registers with the browser's history. You must first wrap your component in the withRouter()
higher-order component。然后,您可以使用 setRouteLeaveHook()
函数,该函数接受具有有效 path
属性 和回调函数的任何 route
对象。
import {Component} from 'react';
import {withRouter} from 'react-router';
class Foo extends Component {
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
}
routerWillLeave(nextState) { // return false to block navigation, true to allow
if (nextState.action === 'POP') {
// handle "Back" button clicks here
}
}
}
export default withRouter(Foo);
我使用了 withrouter hoc 来获取 history prop 并且只写了一个 componentDidMount() 方法:
componentDidMount() {
if (this.props.history.action === "POP") {
// custom back button implementation
}
}
挂钩示例
const {history} = useRouter();
useEffect(() => {
return () => {
// && history.location.pathname === "any specific path")
if (history.action === "POP") {
history.replace(history.location.pathname, /* the new state */);
}
};
}, [history])
我不用history.listen因为不影响状态
const disposeListener = history.listen(navData => {
if (navData.pathname === "/props") {
navData.state = /* the new state */;
}
});
您可以使用 "withrouter" HOC 并使用 this.props.history.goBack
.
<Button onClick={this.props.history.goBack}>
BACK
</Button>
这取决于你在 React 中使用的 Router 类型。
如果您使用 react-router 中的 BrowserRouter(虽然在 react-router v4 中不可用),如上所述,您可以使用操作 'POP' 拦截浏览器后退按钮。
但是,如果您使用HashRouter 来推送路由,上述解决方案将不起作用。原因是当您单击浏览器后退按钮或从您的组件推送路由时,哈希路由器总是通过 'POP' 操作触发。您不能简单地用 window.popstate 或 history.listen 来区分这两个动作。
使用钩子可以检测后退和前进按钮
import { useHistory } from 'react-router-dom'
const [ locationKeys, setLocationKeys ] = useState([])
const history = useHistory()
useEffect(() => {
return history.listen(location => {
if (history.action === 'PUSH') {
setLocationKeys([ location.key ])
}
if (history.action === 'POP') {
if (locationKeys[1] === location.key) {
setLocationKeys(([ _, ...keys ]) => keys)
// Handle forward event
} else {
setLocationKeys((keys) => [ location.key, ...keys ])
// Handle back event
}
}
})
}, [ locationKeys, ])
这个问题的大部分答案要么使用过时版本的 React Router,依赖不太现代的 Class 组件,要么令人困惑;和 none 使用 Typescript,这是一种常见的组合。这是使用 Router v5、函数组件和 Typescript 的答案:
// use destructuring to access the history property of the ReactComponentProps type
function MyComponent( { history }: ReactComponentProps) {
// use useEffect to access lifecycle methods, as componentDidMount etc. are not available on function components.
useEffect(() => {
return () => {
if (history.action === "POP") {
// Code here will run when back button fires. Note that it's after the `return` for useEffect's callback; code before the return will fire after the page mounts, code after when it is about to unmount.
}
}
})
}
可以找到更完整的示例和解释 here。
用于在响应功能组件中对浏览器的按下发出警告。执行以下步骤
- 声明 isBackButtonClicked 并将其初始化为 false 并使用 setBackbuttonPress 函数保持状态。
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
- 在componentdidmount中,添加如下几行代码
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
定义 onBackButtonEvent 函数并根据您的要求编写逻辑。
const onBackButtonEvent = (e) => { e.preventDefault(); if (!isBackButtonClicked) { if (window.confirm("Do you want to go to Test Listing")) { setBackbuttonPress(true) props.history.go(listingpage) } else { window.history.pushState(null, null, window.location.pathname); setBackbuttonPress(false) } }
}
在componentwillmount取消订阅onBackButtonEvent函数
最终代码将如下所示
import React,{useEffect,useState} from 'react'
function HandleBrowserBackButton() {
const [isBackButtonClicked, setBackbuttonPress] = useState(false)
useEffect(() => {
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
//logic for showing popup warning on page refresh
window.onbeforeunload = function () {
return "Data will be lost if you leave the page, are you sure?";
};
return () => {
window.removeEventListener('popstate', onBackButtonEvent);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onBackButtonEvent = (e) => {
e.preventDefault();
if (!isBackButtonClicked) {
if (window.confirm("Do you want to go to Test Listing")) {
setBackbuttonPress(true)
props.history.go(listingpage)
} else {
window.history.pushState(null, null, window.location.pathname);
setBackbuttonPress(false)
}
}
}
return (
<div>
</div>
)
}
export default HandleBrowserBackButton
即将推出的 6.0 版引入了 useBlocker 挂钩 - 可用于拦截所有导航尝试。
import { Action } from 'history';
import { useBlocker } from 'react-router';
// when blocker should be active
const unsavedChanges = true;
useBlocker((transition) => {
const {
location, // The new location
action, // The action that triggered the change
} = transition;
// intercept back and forward actions:
if (action === Action.Pop) {
alert('intercepted!')
}
}, unsavedChanges);
使用钩子。我已将@Nicolas Keller 的代码转换为 typescript
const [locationKeys, setLocationKeys] = useState<(string | undefined)[]>([]);
const history = useHistory();
useEffect(() => {
return history.listen((location) => {
if (history.action === 'PUSH') {
if (location.key) setLocationKeys([location.key]);
}
if (history.action === 'POP') {
if (locationKeys[1] === location.key) {
setLocationKeys(([_, ...keys]) => keys);
// Handle forward event
console.log('forward button');
} else {
setLocationKeys((keys) => [location.key, ...keys]);
// Handle back event
console.log('back button');
removeTask();
}
}
});
}, [locationKeys]);
如果你使用的是React Router V5,可以试试Prompt。
Used to prompt the user before navigating away from a page. When your application enters a state that should prevent the user from navigating away (like a form is half-filled out), render a <Prompt>
<Prompt
message={(location, action) => {
if (action === 'POP') {
console.log("Backing up...")
// Add your back logic here
}
return true;
}}
/>
只需放入 componentDidMount()
componentDidMount() {
window.onbeforeunload =this.beforeUnloadListener;
}
beforeUnloadListener = (event) => {
event.preventDefault();
return event.returnValue = "Are you sure you want to exit?";
};
将这两行添加到您的 componentDidMount() 中。这对我有用
window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', function(event) {
window.location.replace(
`YOUR URL`
);
});
在 NextJs 中,我们可以使用 beforePopState 函数并执行我们想要的操作,例如关闭模态或显示模态或检查后退地址并决定要做什么
const router = useRouter();
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// I only want to allow these two routes!
if (as === '/' ) {
// Have SSR render bad routes as a 404.
window.location.href = as;
closeModal();
return false
}
return true
})
}, [])