【问题标题】:How to use a custom component with react-router route transitions?如何使用带有 react-router 路由转换的自定义组件?
【发布时间】:2016-05-18 10:51:02
【问题描述】:

文章Confirming Navigation 解释了如何在转换钩子中使用浏览器确认框。美好的。但我想使用我自己的对话框。如果我要使用history 模块中的方法,我认为这是可能的。是否可以使用 react-router 中的 setRouteLeaveHook 做到这一点?

【问题讨论】:

标签: javascript react-router


【解决方案1】:

核心问题是setRouteLeaveHook 期望钩子函数同步返回它的结果。这意味着您没有时间显示自定义对话框组件,等待用户单击选项,然后然后返回结果。所以我们需要一种方法来指定一个 asynchronous 钩子。这是我写的一个实用函数:

// Asynchronous version of `setRouteLeaveHook`.
// Instead of synchronously returning a result, the hook is expected to
// return a promise.
function setAsyncRouteLeaveHook(router, route, hook) {
  let withinHook = false
  let finalResult = undefined
  let finalResultSet = false
  router.setRouteLeaveHook(route, nextLocation => {
    withinHook = true
    if (!finalResultSet) {
      hook(nextLocation).then(result => {
        finalResult = result
        finalResultSet = true
        if (!withinHook && nextLocation) {
          // Re-schedule the navigation
          router.push(nextLocation)
        }
      })
    }
    let result = finalResultSet ? finalResult : false
    withinHook = false
    finalResult = undefined
    finalResultSet = false
    return result
  })
}

这里是一个如何使用它的例子,使用vex来显示一个对话框:

componentWillMount() {
  setAsyncRouteLeaveHook(this.context.router, this.props.route, this.routerWillLeave)
}
​
routerWillLeave(nextLocation) {
  return new Promise((resolve, reject) => {
    if (!this.state.textValue) {
      // No unsaved changes -- leave
      resolve(true)
    } else {
      // Unsaved changes -- ask for confirmation
      vex.dialog.confirm({
        message: 'There are unsaved changes. Leave anyway?' + nextLocation,
        callback: result => resolve(result)
      })
    }
  })
}

【讨论】:

  • 谢谢丹尼尔。你是说如果 setRouteLeaveHook 只使用默认的浏览器确认框,这是同步执行的?
  • 仅供参考,这也适用于对话框/对话框 polyfill。
  • 感谢您的解决方案,我想知道router.push(nextLocation) 是否始终有效,无论用户是单击后退还是前进按钮。 push 似乎只暗示一个方向,但也许我弄错了。
  • 非常感谢这种深思熟虑的方法。我根据我们的需要稍微调整了它,效果很好。我通过您对 react-router 功能请求的评论找到了这个答案。这是一个非常常见的界面工作流程,这是我目前找到的最好的(仅可接受的)解决方案。
  • 这会导致实际浏览器返回按钮出现问题
【解决方案2】:

上面的内容很好,除非用户回到历史。类似以下的内容应该可以解决问题:

if (!withinHook && nextLocation) {
    if (nextLocation.action=='POP') {
        router.goBack()
    } else {
      router.push(nextLocation)
    }
}

【讨论】:

    【解决方案3】:

    这是我的解决方案。我制作了一个自定义对话框组件,您可以使用它来包装应用程序中的任何组件。你可以包装你的标题,这样它就会出现在所有页面上。它假定您使用的是 Redux Form,但您可以简单地将 areThereUnsavedChanges 替换为其他一些表单更改检查代码。它还使用 React Bootstrap 模态,您可以再次将其替换为您自己的自定义对话框。

    import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import { withRouter, browserHistory } from 'react-router'
    import { translate } from 'react-i18next'
    import { Button, Modal, Row, Col } from 'react-bootstrap'
    
    // have to use this global var, because setState does things at unpredictable times and dialog gets presented twice
    let navConfirmed = false
    
    @withRouter
    @connect(
      state => ({ form: state.form })
    )
    export default class UnsavedFormModal extends Component {
      constructor(props) {
        super(props)
        this.areThereUnsavedChanges = this.areThereUnsavedChanges.bind(this)
        this.state = ({ unsavedFormDialog: false })
      }
    
      areThereUnsavedChanges() {
        return this.props.form && Object.values(this.props.form).length > 0 &&
          Object.values(this.props.form)
            .findIndex(frm => (Object.values(frm)
              .findIndex(field => field && field.initial && field.initial !== field.value) !== -1)) !== -1
      }
    
      render() {
        const moveForward = () => {
          this.setState({ unsavedFormDialog: false })
          navConfirmed = true
          browserHistory.push(this.state.nextLocation.pathname)
        }
        const onHide = () => this.setState({ unsavedFormDialog: false })
    
        if (this.areThereUnsavedChanges() && this.props.router && this.props.routes && this.props.routes.length > 0) {
          this.props.router.setRouteLeaveHook(this.props.routes[this.props.routes.length - 1], (nextLocation) => {
            if (navConfirmed || !this.areThereUnsavedChanges()) {
              navConfirmed = false
              return true
            } else {
              this.setState({ unsavedFormDialog: true, nextLocation: nextLocation })
              return false
            }
          })
        }
    
        return (
          <div>
            {this.props.children}
            <Modal show={this.state.unsavedFormDialog} onHide={onHide} bsSize="sm" aria-labelledby="contained-modal-title-md">
              <Modal.Header>
                <Modal.Title id="contained-modal-title-md">WARNING: unsaved changes</Modal.Title>
              </Modal.Header>
              <Modal.Body>
                Are you sure you want to leave the page without saving changes to the form?
                <Row>
                  <Col xs={6}><Button block onClick={onHide}>Cancel</Button></Col>
                  <Col xs={6}><Button block onClick={moveForward}>OK</Button></Col>
                </Row>
              </Modal.Body>
            </Modal>
          </div>
        )
      }
    }
    

    【讨论】:

      【解决方案4】:

      我通过设置布尔值来实现它是否已确认导航离开(使用 react-router 2.8.x)。正如您发布的链接中所说: https://github.com/ReactTraining/react-router/blob/master/docs/guides/ConfirmingNavigation.md

      返回 false 以防止在没有提示用户的情况下进行转换

      但是,他们忘记提到钩子也应该取消注册,请参阅 herehere

      我们可以使用它来实现我们自己的解决方案,如下所示:

      class YourComponent extends Component {
        constructor() {
          super();
      
          const {route} = this.props;
          const {router} = this.context;
      
          this.onCancel = this.onCancel.bind(this);
          this.onConfirm = this.onConfirm.bind(this);
      
          this.unregisterLeaveHook = router.setRouteLeaveHook(
            route,
            this.routerWillLeave.bind(this)
          );
        }
      
        componentWillUnmount() {
          this.unregisterLeaveHook();
        }
      
        routerWillLeave() {
          const {hasConfirmed} = this.state;
          if (!hasConfirmed) {
            this.setState({showConfirmModal: true});
      
            // Cancel route change
            return false;
          }
      
          // User has confirmed. Navigate away
          return true;
        }
      
        onCancel() {
          this.setState({showConfirmModal: false});
        }
      
        onConfirm() {
          this.setState({hasConfirmed: true, showConfirmModal: true}, function () {
            this.context.router.goBack();
          }.bind(this));
        }
      
        render() {
          const {showConfirmModal} = this.state;
      
          return (
            <ConfirmModal
              isOpen={showConfirmModal}
              onCancel={this.onCancel}
              onConfirm={this.onConfirm} />
          );
        }
      }
      
      YourComponent.contextTypes = {
        router: routerShape
      };
      

      【讨论】:

        【解决方案5】:

        发布我的拦截返回按钮甚至更改路线的解决方案。这适用于 React-router 2.8 或更高版本。甚至使用 withRouter

        import React, {PropTypes as T} from 'react';
        
        ...
        componentWillMount() {
                this.context.router.setRouteLeaveHook(this.props.route, this.routerWillLeaveCallback.bind(this));
            }
        
            routerWillLeaveCallback(nextLocation) {
                let showModal = this.state.unsavedChanges;
                if (showModal) {
                    this.setState({
                        openUnsavedDialog: true,
                        unsavedResolveCallback: Promise.resolve
                    });
                    return false;
                }
                return true;
            }
        }
        
        
        YourComponent.contextTypes = {
            router: T.object.isRequired
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-03-14
          • 2018-10-14
          • 2018-10-04
          • 1970-01-01
          • 2019-02-06
          • 1970-01-01
          • 2021-05-10
          • 2018-02-04
          相关资源
          最近更新 更多