如何在React表单验证完成前禁止用户导航跳转?
React 表单未完成时拦截导航的实现方案
你原来的实现思路存在根本问题:componentWillUnmount 是组件即将卸载时触发的生命周期,执行到这个钩子的时候路由跳转已经完成,此时调用任何逻辑都无法拦截之前的导航动作,自然会出现先跳转再刷新的问题。
以下是对应场景的可行实现方案:
单页应用路由跳转拦截(React Router 场景)
这是大部分React应用的路由方案,不同版本的React Router API有差异:
React Router v5
直接使用官方提供的 Prompt 组件即可快速实现拦截,不需要手动监听事件:
import { Prompt } from 'react-router-dom'; // 在表单组件的render方法内加入以下代码 <Prompt // 表单校验不通过时开启拦截 when={this.invalidForm()} // 弹出的提示内容 message="您还有必填字段未填写,确定要离开当前页面吗?" />
如果需要用自定义的UI提示而非浏览器默认弹窗,可以用 history.block API手动控制:
componentDidMount() { // 注册导航拦截 this.unblockNavigation = this.props.history.block((targetLocation) => { if (this.invalidForm()) { // 展示自定义校验提示 this.setState({ showValidation: true, pendingNavigateUrl: targetLocation }) // 返回false即阻止导航 return false } return true }) } componentWillUnmount() { // 组件卸载时清除拦截 this.unblockNavigation?.() } // 表单提交成功后手动触发跳转 handleSubmitSuccess = () => { this.unblockNavigation() this.props.history.push(this.state.pendingNavigateUrl) } // 用户主动选择放弃填写直接离开 handleForceLeave = () => { this.unblockNavigation() this.props.history.push(this.state.pendingNavigateUrl) }
React Router v6
v6版本移除了Prompt组件,可使用官方提供的usePrompt hook实现,类组件可通过高阶组件适配:
// 函数组件直接使用 import { usePrompt } from 'react-router-dom' function FormPage() { const [formInvalid, setFormInvalid] = useState(true) // 表单校验不通过时开启拦截 usePrompt('您还有必填字段未填写,确定要离开当前页面吗?', formInvalid) // 其他表单逻辑 }
浏览器刷新/关闭/跳转外部站点拦截
如果需要拦截用户刷新页面、关闭标签页、跳转到外部网站的动作,需要监听beforeunload事件:
componentDidMount() { window.addEventListener('beforeunload', this.handleBeforeUnload) } componentWillUnmount() { window.removeEventListener('beforeunload', this.handleBeforeUnload) } handleBeforeUnload = (e) => { if (this.invalidForm()) { e.preventDefault() e.returnValue = '' return '' } }
注意事项
不要使用window.location.reload这类方式做拦截,不仅无法达到预期效果,还会清空用户已填写的表单内容,体验极差。自定义拦截弹窗时建议给用户提供选择:留在当前页继续填写、或是放弃已填内容直接离开,不要强制用户必须完成填写才能跳转。
内容的提问来源于stack exchange,提问作者jackwh
相关产品推荐
相关产品推荐

