React-router 将弹框Modal嵌入路由,create a modal route with react-router

背景:

  在使用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加进路由的尝试:

方式一: 使用 <Route path="/" /> + this.props.location.search改变路由

- 特点: 通过跳转路由,在原有url上增加"?login=true",PUSH路由,以此判断识别是否显示指定内容

//  - 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>)
        )
    } 

方式二: 在需要页面增加<Route>节点 + path={this.props.match.url + "/modal"} + React.createPortal(<div>...</div>, #modal_root) + index.html中的 <div ></div>

- 特点: 在需要的页面增加<Route>节点,且Route的path为当前url(即this.props.match.url) + "/modal",另外Route的渲染内容(render)和通过React.createPortal和外部index.html的<div ></div>关联

//  - 需要显示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>
        )
    }

最终使用的方法

 1. 使用路由跳转(本处使用的search),但是仅仅跳转,路由中没有实际内容;

2. 原本的Modal显示不变方式;

3. 在Modal的show()方法中,增加history.push({pathname: this.props.match.url, search: "?modal=true"});

4. 增加 history.listen监听,判断带有相应search的路由回退,即可关闭想要Modal;

5. 在Modal的hide()方法中,增加history.go(-1),在Modal隐藏时,回退路由。

引用:

title: Create a Modal Route with React Router

author: Jason Brown

url: https://codedaily.io/tutorials/77/Create-a-Modal-Route-with-React-Router

github: https://github.com/codedailyio/ReactRouterModal