如何用React Router <Prompt>自定义组件控制路由跳转
如何用自定义组件替代React Router 的默认确认弹窗?
我完全懂你遇到的痛点——默认的window.confirm是阻塞式的,它会卡住JS执行直到用户做出选择,所以那个callback能一直待在作用域里。但换成自定义组件后,getConfirmation函数跑完就结束了,等用户点击弹窗按钮时,原来的callback早就找不到了。别慌,咱们调整一下代码就能解决这个问题。
问题根源
默认的window.confirm是同步阻塞的,函数不会继续往下走,callback一直保存在内存里。但自定义弹窗是异步的,getConfirmation执行完就退出了,再加上React的状态更新是异步的,你用useState存callback的话,组件重新渲染前,弹窗拿到的可能还是旧的或者空的callback引用。
解决方案:用useRef保存callback
useRef的.current属性是个可变容器,它不会触发组件重新渲染,能一直保留最新的callback引用。咱们把代码改成这样:
修改后的完整代码
import React, { useState, useRef } from "react"; import ReactDOM from "react-dom"; import { BrowserRouter as Router, Route, Switch, Link, Prompt } from "react-router-dom"; import "./styles.css"; function App() { console.log("Rendering App..."); const [confirm, setConfirm] = useState(false); // 用useRef代替useState保存callback const confirmCallbackRef = useRef(null); function getConfirmation(message, callback) { console.log("Inside getConfirmation function..."); // 直接把callback存到ref的current属性里 confirmCallbackRef.current = callback; setConfirm(true); } // 定义允许跳转的处理函数 const allowTransition = () => { setConfirm(false); if (confirmCallbackRef.current) { confirmCallbackRef.current(true); // 执行后清空ref,避免重复调用 confirmCallbackRef.current = null; } }; // 定义阻止跳转的处理函数 const blockTransition = () => { setConfirm(false); if (confirmCallbackRef.current) { confirmCallbackRef.current(false); confirmCallbackRef.current = null; } }; return ( <Router getUserConfirmation={getConfirmation}> <AllRoutes /> {confirm && ( <Confirm allowTransition={allowTransition} blockTransition={blockTransition} /> )} </Router> ); } function Confirm(props) { return ( <React.Fragment> <div>Are you sure you want to leave?</div> <button onClick={props.allowTransition}>Yes, proceed</button> <button onClick={props.blockTransition}>Stay here</button> </React.Fragment> ); } function AllRoutes(props) { console.log("Rendering AllRoutes..."); return ( <Switch> <Route exact path="/" component={Home} /> <Route exact path="/comp1" component={Component1} /> </Switch> ); } function Home(props) { console.log("Rendering Home..."); return ( <React.Fragment> <div>This is Home</div> <ul> <li> <Link to="/comp1">Component1</Link> </li> </ul> </React.Fragment> ); } function Component1(props) { console.log("Rendering Component1..."); const [isBlocking, setIsBlocking] = useState(true); return ( <React.Fragment> <Prompt when={isBlocking} message={location => `Are you sure you want to go to ${location.pathname}` } /> <div>This is component 1</div> <Link to="/">Home</Link> {/* 可以加个按钮测试取消阻塞 */} <button onClick={() => setIsBlocking(false)}>Allow navigation</button> </React.Fragment> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
为什么这样有效?
useRef的.current是可变的,不会像useState那样因为状态更新触发组件重新渲染,所以getConfirmation里存的callback会一直保留到用户点击按钮的时候。- 我们把处理跳转的逻辑放在App组件里,Confirm组件只负责触发对应的函数,避免了直接传递
callback可能带来的引用丢失问题。 - 执行完
callback后清空ref,防止用户多次触发跳转时重复调用旧的callback。
这样修改后,你的自定义弹窗就能完美替代默认的window.confirm,实现路由跳转的确认功能啦!
内容的提问来源于stack exchange,提问作者cbdeveloper
相关产品推荐
相关产品推荐

