You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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);

为什么这样有效?

  1. useRef的.current是可变的,不会像useState那样因为状态更新触发组件重新渲染,所以getConfirmation里存的callback会一直保留到用户点击按钮的时候。
  2. 我们把处理跳转的逻辑放在App组件里,Confirm组件只负责触发对应的函数,避免了直接传递callback可能带来的引用丢失问题。
  3. 执行完callback后清空ref,防止用户多次触发跳转时重复调用旧的callback。

这样修改后,你的自定义弹窗就能完美替代默认的window.confirm,实现路由跳转的确认功能啦!

内容的提问来源于stack exchange,提问作者cbdeveloper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:40:11