背景:
在使用react-router时,我们往往只有在真实的需要页面跳转时,才会将页面加入路由(Route)。对于弹框(Modal),却总是会忽视它的路由需求。
在开发者眼中: 弹框只是页面中的附属,无需单独准备路由存放。当然,也有其他原因,比如我们想要一个公用的方法,以此来显示弹框。诸如此类,导致开发时没有正视弹框。
可在用户眼中: 弹框也是页面,至少在想要返回的时候,移除弹框即可(不要把弹框下的页面也给返回了)。
因此,Modal弹框、Route路由、BackPress物理返回 三者间的微妙关系就变得尤为关键了
目的:
用户视角: 弹框显示时,物理返回(手机返回操作、浏览器左上角回退按钮),只隐藏弹框(不影响下层页面内容,比如滚动、比如输入框内容)
开发者视角: 1. 原本的目的很简单,只需要在有弹框显示时,物理返回隐藏弹框的同时阻止下层页面的返回
2. 将弹框加入路由,弹框即页面,会被路由同等对待
几次尝试:
1. 使用 popstate 监听
window.addEventListener("popstate", () => {});
2. 使用history.listen监听
history.listen((newLocation, action) => { if(action === "PUSH) { } else { // 判断是否存在弹框 if(ReactDOM.findDOMNode(document.getElementById("modal"))) { // 隐藏弹框
// 前进一个路由 history.go(1) } } })
注意: 以上方法均是通过监听路由变化(popstate监听、history.listen监听),并辅以判断弹框的显示与否来确定。
此处使用的是history.go(1)前进一个路由,因为无法阻止在物理返回时的路由回退,只能通过 回退 - 判断有弹框 - 前进 的流程进行
将Modal加进路由的尝试:
// - Router.js中使用: <BrowserRouter> <Switch> ... </Switch> <Route path="/" component={Login}> </BrowserRouter> // - 显示Modal(跳转Login路由): <Linl to={{pathname: this.props.match.url, search: "?login=true"}}>To Login</Link> // - Login.js中使用 render() { let params = new URLSearchParams(this.props.location.search); return( params.get("login") && (<Modal>...</Modal>) ) }
// - 需要显示Modal的 profile.js <Link to={this.props.match.url + "/modal"}>Show Modal</Link> <Route path={this.props.match.url + "/modal"} render={() => { return ( <Modal onClick={() => this.props.history.goBack()}> ... </Modal> ) }} /> // - Modal.js 内容: render() { return React.createPortal( <div onClick={this.props.onClick}> ... </div> ) }
最终使用的方法
引用:
title: Create a Modal Route with React Router
author: Jason Brown
url: https://codedaily.io/tutorials/77/Create-a-Modal-Route-with-React-Router