Javascript 如何将自定义组件与react路由器路由转换一起使用?
本文介绍了如何在过渡挂钩中使用浏览器确认框。好的但是我想使用我自己的对话框。如果我使用Javascript 如何将自定义组件与react路由器路由转换一起使用?,javascript,react-router,Javascript,React Router,本文介绍了如何在过渡挂钩中使用浏览器确认框。好的但是我想使用我自己的对话框。如果我使用history模块中的方法,我认为这是可能的。使用react router中的setRouteLeaveHook可以做到这一点吗?核心问题是setRouteLeaveHook希望钩子函数同步返回其结果。这意味着您没有时间显示自定义对话框组件,等待用户单击某个选项,然后返回结果。所以我们需要一种方法来指定异步钩子。这是我写的一个实用函数: // Asynchronous version of `setRouteL
history
模块中的方法,我认为这是可能的。使用react router中的setRouteLeaveHook
可以做到这一点吗?核心问题是setRouteLeaveHook
希望钩子函数同步返回其结果。这意味着您没有时间显示自定义对话框组件,等待用户单击某个选项,然后返回结果。所以我们需要一种方法来指定异步钩子。这是我写的一个实用函数:
// 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
})
}
以下是如何使用它的示例,使用以显示对话框:
componentWillMount() {
setAsyncRouteLeaveHook(this.context.router, this.props.route, this.routerWillLeave)
}
routerWillLeave() {
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)
})
}
})
}
以上是伟大的,除非用户回到历史。类似于以下内容的内容可以解决此问题:
if (!withinHook && nextLocation) {
if (nextLocation.action=='POP') {
router.goBack()
} else {
router.push(nextLocation)
}
}
这是我的解决方案。我制作了一个自定义的对话框组件,你可以用它来包装应用程序中的任何组件。你可以包装你的标题,这样它就出现在所有的页面上。它假设您使用的是Redux表单,但您可以简单地用一些其他表单更改检查代码替换
AreUnsensevedChanges
。它还使用React引导模式,您也可以用自己的自定义对话框替换它
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>
)
}
}
import React,{Component}来自“React”
从“react redux”导入{connect}
从“react router”导入{withRouter,browserHistory}
从“react-i18next”导入{translate}
从“react bootstrap”导入{按钮、模式、行、列}
//必须使用这个全局变量,因为setState在不可预测的时间执行操作,并且对话框会显示两次
让navconfirm=false
@带路由器
@连接(
state=>({form:state.form})
)
导出默认类UnsavedFormModal扩展组件{
建造师(道具){
超级(道具)
this.arethensavedchanges=this.arethensavedchanges.bind(this)
this.state=({unsavedFormDialog:false})
}
是否需要更改{
返回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(){
常量向前移动=()=>{
this.setState({unsavedFormDialog:false})
navconfirm=true
browserHistory.push(this.state.nextLocation.pathname)
}
const onHide=()=>this.setState({unsavedFormDialog:false})
if(this.areshensavedchanges()&&this.props.router&&this.props.routes&&this.props.routes.length>0){
this.props.router.setRouteLeaveHook(this.props.routes[this.props.routes.length-1],(nextLocation)=>{
如果(navconfirm | |!this.aresonsevensavedchanges()){
navconfirm=false
返回真值
}否则{
this.setState({unsavedFormDialog:true,nextLocation:nextLocation})
返回错误
}
})
}
返回(
{this.props.children}
警告:未保存的更改
是否确实要在不保存表单更改的情况下离开页面?
取消
好啊
)
}
}
我通过设置一个布尔开启状态(无论您是否已确认导航离开)(使用react router 2.8.x)实现了这一功能。正如你发布的链接中所说:
返回false以防止不提示用户的转换
但是,他们忘了提到钩子也应该注销,请参见和
我们可以使用它来实施我们自己的解决方案,如下所示:
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
};
class YourComponent扩展组件{
构造函数(){
超级();
const{route}=this.props;
const{router}=this.context;
this.onCancel=this.onCancel.bind(this);
this.onConfirm=this.onConfirm.bind(this);
this.unregisterLeaveHook=router.setRouteLeaveHook(
路线,,
this.routerWillLeave.bind(this)
);
}
组件将卸载(){
此函数为.unregisterLeaveHook();
}
路途漫漫{
const{hasconformed}=this.state;
如果(!已确认){
this.setState({showConfirmModal:true});
//取消路线变更
返回false;
}
//用户已确认。请导航离开
返回true;
}
onCancel(){
this.setState({showConfirmModal:false});
}
onConfirm(){
this.setState({hasConfirm:true,showConfirmModal:true},函数(){
this.context.router.goBack();
}.约束(这个);
}
render(){
const{showConfirmModal}=this.state;
返回(
);
}
}
YourComponent.contextTypes={
路由器:路由器
};
发布我的解决方案,以拦截后退按钮,甚至更改路线。这适用于React router 2.8或更高版本。甚至使用路由器
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
};
完整性:这是一个链接,显示如何在使用历史记录模块时添加自定义对话框。谢谢你,丹尼尔。你是说如果setRouteLeaveHook只使用默认的浏览器确认框,这是同步执行的吗?仅供参考,这也适用于dialog/dialog polyfill。感谢您的解决方案,我想知道无论用户单击后退还是前进按钮,
router.push(nextLocation)
是否始终有效。这看起来像是push
只意味着一个方向,但可能我弄错了。非常感谢这种深思熟虑的方法。我根据我们的需要稍微调整了一下,效果很好。我通过您对react路由器功能请求的评论找到了这个答案。这是一个非常常见的界面工作流,这是我目前找到的最好(唯一可以接受的)解决方案。这会导致实际的浏览器后退按钮出现问题