Skip to content

编程式导航

概述

编程式导航是指通过JavaScript代码而非用户点击链接来控制页面跳转。React Router v6提供了useNavigate Hook来实现编程式导航,支持前进、后退、替换历史记录等多种导航方式。

useNavigate Hook详解

基础用法

jsx
import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();
  const [credentials, setCredentials] = useState({ email: '', password: '' });

  const handleLogin = async (e) => {
    e.preventDefault();
    
    try {
      const user = await loginAPI(credentials);
      
      // 登录成功后导航到仪表板
      navigate('/dashboard');
      
    } catch (error) {
      console.error('Login failed:', error);
    }
  };

  const handleCancel = () => {
    // 返回上一页
    navigate(-1);
  };

  const handleSignUp = () => {
    // 导航到注册页面
    navigate('/register');
  };

  return (
    <form onSubmit={handleLogin}>
      <input
        type="email"
        value={credentials.email}
        onChange={(e) => setCredentials(prev => ({
          ...prev,
          email: e.target.value
        }))}
        placeholder="Email"
      />
      
      <input
        type="password"
        value={credentials.password}
        onChange={(e) => setCredentials(prev => ({
          ...prev,
          password: e.target.value
        }))}
        placeholder="Password"
      />

      <div className="form-actions">
        <button type="submit">Login</button>
        <button type="button" onClick={handleCancel}>Cancel</button>
        <button type="button" onClick={handleSignUp}>Sign Up</button>
      </div>
    </form>
  );
}

导航选项

jsx
function NavigationExamples() {
  const navigate = useNavigate();

  const examples = {
    // 基础导航
    basicNavigation: () => {
      navigate('/dashboard');
    },

    // 替换当前历史记录条目
    replaceNavigation: () => {
      navigate('/dashboard', { replace: true });
    },

    // 带状态的导航
    navigationWithState: () => {
      navigate('/profile', {
        state: {
          from: 'dashboard',
          user: currentUser,
          timestamp: Date.now()
        }
      });
    },

    // 相对导航
    relativeNavigation: () => {
      navigate('../parent'); // 上一级
      navigate('child');     // 子级
      navigate('./sibling'); // 同级
    },

    // 历史记录导航
    historyNavigation: () => {
      navigate(-1); // 后退一页
      navigate(-2); // 后退两页
      navigate(1);  // 前进一页
    },

    // 带查询参数的导航
    navigationWithQuery: () => {
      navigate('/search?q=react&category=books&page=1');
    },

    // 使用URLSearchParams构建查询参数
    navigationWithSearchParams: () => {
      const params = new URLSearchParams();
      params.set('category', 'electronics');
      params.set('minPrice', '100');
      params.set('maxPrice', '500');
      
      navigate(`/products?${params.toString()}`);
    }
  };

  return (
    <div className="navigation-examples">
      <h2>导航示例</h2>
      
      <div className="example-buttons">
        <button onClick={examples.basicNavigation}>
          Basic Navigation
        </button>
        
        <button onClick={examples.replaceNavigation}>
          Replace Navigation
        </button>
        
        <button onClick={examples.navigationWithState}>
          Navigation with State
        </button>
        
        <button onClick={examples.relativeNavigation}>
          Relative Navigation
        </button>
        
        <button onClick={examples.historyNavigation}>
          History Navigation
        </button>
        
        <button onClick={examples.navigationWithQuery}>
          Navigation with Query
        </button>
        
        <button onClick={examples.navigationWithSearchParams}>
          Navigation with SearchParams
        </button>
      </div>
    </div>
  );
}

条件导航

基于认证状态的导航

jsx
// 认证相关的导航逻辑
function useAuthNavigation() {
  const navigate = useNavigate();
  const location = useLocation();
  const { user, isLoading } = useAuth();

  const navigateAfterLogin = useCallback((user) => {
    // 获取登录前尝试访问的页面
    const intendedPath = location.state?.from || '/dashboard';
    
    // 根据用户角色决定导航目标
    if (user.role === 'admin') {
      navigate('/admin/dashboard', { replace: true });
    } else if (user.needsOnboarding) {
      navigate('/onboarding', { replace: true });
    } else {
      navigate(intendedPath, { replace: true });
    }
  }, [navigate, location.state]);

  const navigateAfterLogout = useCallback(() => {
    // 清除敏感页面的历史记录
    navigate('/', { replace: true });
  }, [navigate]);

  const requireAuth = useCallback((callback) => {
    return (...args) => {
      if (!user) {
        navigate('/login', {
          state: { from: location.pathname },
          replace: true
        });
        return;
      }
      
      callback(...args);
    };
  }, [user, navigate, location.pathname]);

  return {
    navigateAfterLogin,
    navigateAfterLogout,
    requireAuth
  };
}

// 使用认证导航
function ProtectedComponent() {
  const { requireAuth } = useAuthNavigation();

  const handleSensitiveAction = requireAuth(() => {
    // 这个函数只有在用户已认证时才会执行
    performSensitiveOperation();
  });

  return (
    <button onClick={handleSensitiveAction}>
      Sensitive Action
    </button>
  );
}

// 基于权限的导航
function usePermissionNavigation() {
  const navigate = useNavigate();
  const { user, permissions } = useAuth();

  const navigateWithPermissionCheck = useCallback((path, requiredPermission, options = {}) => {
    if (!user) {
      navigate('/login', {
        state: { from: path },
        replace: true
      });
      return false;
    }

    if (requiredPermission && !permissions.includes(requiredPermission)) {
      navigate('/access-denied', {
        state: { 
          requiredPermission,
          attemptedPath: path
        },
        replace: options.replace
      });
      return false;
    }

    navigate(path, options);
    return true;
  }, [navigate, user, permissions]);

  return { navigateWithPermissionCheck };
}

业务逻辑导航

jsx
// 购物流程导航
function useCheckoutNavigation() {
  const navigate = useNavigate();
  const { cart, user } = useStore();

  const startCheckout = useCallback(() => {
    // 检查购物车
    if (!cart || cart.items.length === 0) {
      navigate('/cart', {
        state: { message: 'Your cart is empty' }
      });
      return;
    }

    // 检查登录状态
    if (!user) {
      navigate('/login', {
        state: { 
          from: '/checkout',
          message: 'Please login to continue checkout' 
        }
      });
      return;
    }

    // 检查配送地址
    if (!user.defaultAddress) {
      navigate('/checkout/shipping', {
        state: { step: 'address' }
      });
      return;
    }

    // 进入结账流程
    navigate('/checkout');
  }, [navigate, cart, user]);

  const proceedToPayment = useCallback(async (shippingData) => {
    try {
      // 保存配送信息
      await saveShippingInfo(shippingData);
      
      navigate('/checkout/payment', {
        state: { shippingConfirmed: true }
      });
      
    } catch (error) {
      console.error('Failed to save shipping info:', error);
      // 可以显示错误而不导航
    }
  }, [navigate]);

  const completeOrder = useCallback(async (orderData) => {
    try {
      const order = await createOrder(orderData);
      
      // 订单创建成功,导航到成功页面
      navigate('/checkout/success', {
        state: { 
          orderId: order.id,
          orderNumber: order.orderNumber
        },
        replace: true // 防止用户返回到支付页面
      });
      
    } catch (error) {
      navigate('/checkout/error', {
        state: { 
          error: error.message,
          orderData 
        }
      });
    }
  }, [navigate]);

  return {
    startCheckout,
    proceedToPayment,
    completeOrder
  };
}

// 表单提交导航
function useFormNavigation() {
  const navigate = useNavigate();

  const handleFormSubmit = useCallback(async (formData, config = {}) => {
    const {
      successPath = '/success',
      errorPath = null,
      keepFormData = false,
      showSuccessMessage = true
    } = config;

    try {
      const result = await submitForm(formData);
      
      const navigationState = {
        success: true,
        message: showSuccessMessage ? 'Form submitted successfully' : null,
        result
      };

      if (keepFormData) {
        navigationState.formData = formData;
      }

      navigate(successPath, {
        state: navigationState,
        replace: config.replaceHistory
      });

    } catch (error) {
      if (errorPath) {
        navigate(errorPath, {
          state: {
            error: error.message,
            formData: keepFormData ? formData : null
          }
        });
      } else {
        // 留在当前页面显示错误
        console.error('Form submission failed:', error);
      }
    }
  }, [navigate]);

  return { handleFormSubmit };
}

导航拦截和确认

离开页面确认

jsx
import { useBeforeUnload, useBlocker } from 'react-router-dom';

function FormWithUnsavedChanges() {
  const navigate = useNavigate();
  const [formData, setFormData] = useState({ title: '', content: '' });
  const [isDirty, setIsDirty] = useState(false);
  const [isSaving, setIsSaving] = useState(false);

  // 阻止浏览器刷新/关闭时丢失未保存的数据
  useBeforeUnload(
    useCallback((event) => {
      if (isDirty && !isSaving) {
        event.preventDefault();
        return (event.returnValue = 'You have unsaved changes. Are you sure you want to leave?');
      }
    }, [isDirty, isSaving]),
    { capture: true }
  );

  // 阻止路由导航
  const blocker = useBlocker(
    ({ currentLocation, nextLocation }) => {
      return isDirty && 
             !isSaving && 
             currentLocation.pathname !== nextLocation.pathname;
    }
  );

  const handleFieldChange = (field, value) => {
    setFormData(prev => ({ ...prev, [field]: value }));
    setIsDirty(true);
  };

  const handleSave = async () => {
    setIsSaving(true);
    
    try {
      await saveFormData(formData);
      setIsDirty(false);
      navigate('/success');
    } catch (error) {
      console.error('Save failed:', error);
    } finally {
      setIsSaving(false);
    }
  };

  const handleDiscard = () => {
    setFormData({ title: '', content: '' });
    setIsDirty(false);
    navigate('/');
  };

  // 处理导航确认
  const handleNavigationConfirm = (shouldNavigate) => {
    if (shouldNavigate) {
      setIsDirty(false); // 允许导航
      blocker.proceed();
    } else {
      blocker.reset();
    }
  };

  return (
    <div className="form-page">
      <form>
        <input
          type="text"
          value={formData.title}
          onChange={(e) => handleFieldChange('title', e.target.value)}
          placeholder="Title"
        />
        
        <textarea
          value={formData.content}
          onChange={(e) => handleFieldChange('content', e.target.value)}
          placeholder="Content"
          rows={10}
        />

        <div className="form-actions">
          <button type="button" onClick={handleSave} disabled={isSaving}>
            {isSaving ? 'Saving...' : 'Save'}
          </button>
          
          <button type="button" onClick={handleDiscard}>
            Discard
          </button>
        </div>

        {isDirty && (
          <div className="unsaved-warning">
            You have unsaved changes.
          </div>
        )}
      </form>

      {/* 导航确认对话框 */}
      {blocker.state === 'blocked' && (
        <div className="navigation-confirm-modal">
          <div className="modal-content">
            <h3>Unsaved Changes</h3>
            <p>You have unsaved changes. Are you sure you want to leave?</p>
            
            <div className="modal-actions">
              <button onClick={() => handleNavigationConfirm(true)}>
                Leave without saving
              </button>
              <button onClick={() => handleNavigationConfirm(false)}>
                Stay on page
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// 通用的离开确认Hook
function useUnsavedChangesWarning(hasUnsavedChanges, message = 'You have unsaved changes.') {
  const blocker = useBlocker(
    ({ currentLocation, nextLocation }) => {
      return hasUnsavedChanges && 
             currentLocation.pathname !== nextLocation.pathname;
    }
  );

  useBeforeUnload(
    useCallback((event) => {
      if (hasUnsavedChanges) {
        event.preventDefault();
        return (event.returnValue = message);
      }
    }, [hasUnsavedChanges, message]),
    { capture: true }
  );

  return blocker;
}

导航历史管理

jsx
// 自定义历史管理Hook
function useNavigationHistory() {
  const navigate = useNavigate();
  const location = useLocation();
  const [history, setHistory] = useState([]);
  const [currentIndex, setCurrentIndex] = useState(-1);

  // 记录导航历史
  useEffect(() => {
    setHistory(prev => {
      const newHistory = [...prev.slice(0, currentIndex + 1), location.pathname];
      setCurrentIndex(newHistory.length - 1);
      return newHistory;
    });
  }, [location.pathname]);

  const goBack = useCallback(() => {
    if (currentIndex > 0) {
      const previousPath = history[currentIndex - 1];
      navigate(previousPath);
    }
  }, [navigate, history, currentIndex]);

  const goForward = useCallback(() => {
    if (currentIndex < history.length - 1) {
      const nextPath = history[currentIndex + 1];
      navigate(nextPath);
    }
  }, [navigate, history, currentIndex]);

  const canGoBack = currentIndex > 0;
  const canGoForward = currentIndex < history.length - 1;

  const getHistoryInfo = () => ({
    history,
    currentIndex,
    currentPath: history[currentIndex],
    canGoBack,
    canGoForward
  });

  return {
    goBack,
    goForward,
    canGoBack,
    canGoForward,
    history,
    getHistoryInfo
  };
}

// 导航历史组件
function NavigationHistory() {
  const { 
    goBack, 
    goForward, 
    canGoBack, 
    canGoForward, 
    history, 
    getHistoryInfo 
  } = useNavigationHistory();

  const historyInfo = getHistoryInfo();

  return (
    <div className="navigation-history">
      <div className="history-controls">
        <button onClick={goBack} disabled={!canGoBack}>
          ← Back
        </button>
        
        <button onClick={goForward} disabled={!canGoForward}>
          Forward →
        </button>
        
        <span className="history-position">
          {historyInfo.currentIndex + 1} of {history.length}
        </span>
      </div>

      <div className="history-list">
        <h4>Navigation History:</h4>
        <ol>
          {history.map((path, index) => (
            <li
              key={index}
              className={index === historyInfo.currentIndex ? 'current' : ''}
            >
              {path}
            </li>
          ))}
        </ol>
      </div>
    </div>
  );
}

复杂导航场景

多步骤向导导航

jsx
// 多步骤表单导航
function useWizardNavigation(steps, currentStep) {
  const navigate = useNavigate();
  const [completedSteps, setCompletedSteps] = useState(new Set());

  const goToStep = useCallback((stepIndex) => {
    const step = steps[stepIndex];
    if (!step) return;

    navigate(`/wizard/${step.path}`, {
      state: {
        stepIndex,
        totalSteps: steps.length
      }
    });
  }, [navigate, steps]);

  const nextStep = useCallback(() => {
    const currentIndex = steps.findIndex(step => step.path === currentStep);
    const nextIndex = currentIndex + 1;
    
    if (nextIndex < steps.length) {
      // 标记当前步骤为完成
      setCompletedSteps(prev => new Set([...prev, currentIndex]));
      goToStep(nextIndex);
    }
  }, [currentStep, steps, goToStep]);

  const previousStep = useCallback(() => {
    const currentIndex = steps.findIndex(step => step.path === currentStep);
    const prevIndex = currentIndex - 1;
    
    if (prevIndex >= 0) {
      goToStep(prevIndex);
    }
  }, [currentStep, steps, goToStep]);

  const jumpToStep = useCallback((stepIndex) => {
    // 只能跳转到已完成的步骤或下一步
    const currentIndex = steps.findIndex(step => step.path === currentStep);
    
    if (stepIndex <= currentIndex + 1 || completedSteps.has(stepIndex)) {
      goToStep(stepIndex);
      return true;
    }
    
    return false;
  }, [currentStep, steps, completedSteps, goToStep]);

  const canGoNext = useCallback(() => {
    const currentIndex = steps.findIndex(step => step.path === currentStep);
    return currentIndex < steps.length - 1;
  }, [currentStep, steps]);

  const canGoPrevious = useCallback(() => {
    const currentIndex = steps.findIndex(step => step.path === currentStep);
    return currentIndex > 0;
  }, [currentStep, steps]);

  return {
    nextStep,
    previousStep,
    jumpToStep,
    goToStep,
    canGoNext: canGoNext(),
    canGoPrevious: canGoPrevious(),
    completedSteps
  };
}

// 向导组件
function SetupWizard() {
  const { pathname } = useLocation();
  const currentStep = pathname.split('/').pop();

  const steps = [
    { path: 'welcome', title: 'Welcome', component: WelcomeStep },
    { path: 'account', title: 'Account Setup', component: AccountStep },
    { path: 'preferences', title: 'Preferences', component: PreferencesStep },
    { path: 'verification', title: 'Verification', component: VerificationStep },
    { path: 'complete', title: 'Complete', component: CompleteStep }
  ];

  const {
    nextStep,
    previousStep,
    jumpToStep,
    canGoNext,
    canGoPrevious,
    completedSteps
  } = useWizardNavigation(steps, currentStep);

  const currentStepIndex = steps.findIndex(step => step.path === currentStep);
  const currentStepData = steps[currentStepIndex];

  return (
    <div className="setup-wizard">
      {/* 进度指示器 */}
      <div className="wizard-progress">
        {steps.map((step, index) => (
          <div
            key={step.path}
            className={`progress-step ${
              index === currentStepIndex ? 'current' :
              completedSteps.has(index) ? 'completed' : 'pending'
            }`}
            onClick={() => jumpToStep(index)}
            style={{ cursor: completedSteps.has(index) || index <= currentStepIndex + 1 ? 'pointer' : 'default' }}
          >
            <span className="step-number">{index + 1}</span>
            <span className="step-title">{step.title}</span>
          </div>
        ))}
      </div>

      {/* 步骤内容 */}
      <div className="wizard-content">
        <Routes>
          {steps.map(step => (
            <Route
              key={step.path}
              path={step.path}
              element={
                <step.component
                  onNext={nextStep}
                  onPrevious={previousStep}
                  canGoNext={canGoNext}
                  canGoPrevious={canGoPrevious}
                />
              }
            />
          ))}
        </Routes>
      </div>
    </div>
  );
}

// 向导步骤组件示例
function AccountStep({ onNext, onPrevious, canGoNext, canGoPrevious }) {
  const [accountData, setAccountData] = useState({
    username: '',
    email: '',
    password: ''
  });
  const [isValid, setIsValid] = useState(false);

  useEffect(() => {
    const valid = accountData.username && 
                  accountData.email && 
                  accountData.password.length >= 8;
    setIsValid(valid);
  }, [accountData]);

  const handleNext = () => {
    if (isValid) {
      // 保存账户数据
      saveAccountData(accountData);
      onNext();
    }
  };

  return (
    <div className="wizard-step">
      <h2>Account Setup</h2>
      
      <div className="step-content">
        <input
          type="text"
          value={accountData.username}
          onChange={(e) => setAccountData(prev => ({
            ...prev,
            username: e.target.value
          }))}
          placeholder="Username"
        />
        
        <input
          type="email"
          value={accountData.email}
          onChange={(e) => setAccountData(prev => ({
            ...prev,
            email: e.target.value
          }))}
          placeholder="Email"
        />
        
        <input
          type="password"
          value={accountData.password}
          onChange={(e) => setAccountData(prev => ({
            ...prev,
            password: e.target.value
          }))}
          placeholder="Password"
        />
      </div>

      <div className="step-actions">
        <button 
          onClick={onPrevious} 
          disabled={!canGoPrevious}
        >
          Previous
        </button>
        
        <button 
          onClick={handleNext} 
          disabled={!canGoNext || !isValid}
        >
          Next
        </button>
      </div>
    </div>
  );
}

动态导航路径

jsx
// 基于数据动态决定导航目标
function useDynamicNavigation() {
  const navigate = useNavigate();
  const { user } = useAuth();

  const navigateBasedOnUserType = useCallback((baseAction) => {
    const userRoutes = {
      'premium': `/premium/${baseAction}`,
      'business': `/business/${baseAction}`,
      'enterprise': `/enterprise/${baseAction}`,
      'free': `/free/${baseAction}`
    };

    const targetPath = userRoutes[user.type] || `/free/${baseAction}`;
    navigate(targetPath);
  }, [navigate, user]);

  const navigateToUserDashboard = useCallback(() => {
    const dashboardRoutes = {
      'admin': '/admin/dashboard',
      'manager': '/manager/dashboard',
      'employee': '/employee/dashboard',
      'customer': '/customer/dashboard'
    };

    const dashboardPath = dashboardRoutes[user.role] || '/dashboard';
    navigate(dashboardPath);
  }, [navigate, user]);

  const navigateWithFallback = useCallback((preferredPath, fallbackPath) => {
    // 检查用户是否有访问首选路径的权限
    if (hasAccessToPath(user, preferredPath)) {
      navigate(preferredPath);
    } else {
      navigate(fallbackPath);
    }
  }, [navigate, user]);

  return {
    navigateBasedOnUserType,
    navigateToUserDashboard,
    navigateWithFallback
  };
}

// 条件导航组件
function ConditionalNavigationButton({ children, targetPath, condition, fallbackPath }) {
  const navigate = useNavigate();

  const handleClick = () => {
    if (condition) {
      navigate(targetPath);
    } else if (fallbackPath) {
      navigate(fallbackPath);
    } else {
      console.warn('Navigation condition not met and no fallback provided');
    }
  };

  return (
    <button onClick={handleClick} disabled={!condition && !fallbackPath}>
      {children}
    </button>
  );
}

// 使用
function Dashboard() {
  const { user } = useAuth();

  return (
    <div className="dashboard">
      <h1>Welcome, {user.name}!</h1>
      
      <div className="dashboard-actions">
        <ConditionalNavigationButton
          targetPath="/admin/users"
          condition={user.role === 'admin'}
          fallbackPath="/access-denied"
        >
          Manage Users
        </ConditionalNavigationButton>
        
        <ConditionalNavigationButton
          targetPath="/premium/features"
          condition={user.plan === 'premium'}
          fallbackPath="/upgrade"
        >
          Premium Features
        </ConditionalNavigationButton>
      </div>
    </div>
  );
}

导航事件和监听

导航事件处理

jsx
// 导航事件监听Hook
function useNavigationEvents() {
  const location = useLocation();
  const navigate = useNavigate();
  const [navigationHistory, setNavigationHistory] = useState([]);

  useEffect(() => {
    const navigationEvent = {
      path: location.pathname,
      search: location.search,
      hash: location.hash,
      timestamp: Date.now(),
      referrer: document.referrer
    };

    setNavigationHistory(prev => [...prev, navigationEvent]);

    // 发送导航事件到分析服务
    analytics.track('navigation', navigationEvent);

    // 更新页面标题
    updatePageTitle(location.pathname);

    // 滚动到顶部(可选)
    window.scrollTo(0, 0);

  }, [location]);

  const getNavigationStats = useCallback(() => {
    const stats = navigationHistory.reduce((acc, event) => {
      acc[event.path] = (acc[event.path] || 0) + 1;
      return acc;
    }, {});

    return {
      totalNavigations: navigationHistory.length,
      uniquePaths: Object.keys(stats).length,
      mostVisited: Object.entries(stats)
        .sort(([,a], [,b]) => b - a)
        .slice(0, 5)
    };
  }, [navigationHistory]);

  return {
    navigationHistory,
    getNavigationStats
  };
}

// 页面标题管理
function updatePageTitle(pathname) {
  const titleMap = {
    '/': 'Home - My App',
    '/dashboard': 'Dashboard - My App',
    '/profile': 'Profile - My App',
    '/settings': 'Settings - My App'
  };

  // 动态标题生成
  const generateTitle = (path) => {
    // 处理动态路由
    if (path.startsWith('/users/')) {
      return 'User Profile - My App';
    }
    if (path.startsWith('/products/')) {
      return 'Product Detail - My App';
    }
    
    return titleMap[path] || 'My App';
  };

  document.title = generateTitle(pathname);
}

// 导航监听组件
function NavigationListener() {
  const location = useLocation();
  const { user } = useAuth();

  useEffect(() => {
    // 记录页面访问
    logPageVisit(location.pathname, user?.id);

    // 检查页面权限
    if (!hasPageAccess(user, location.pathname)) {
      navigate('/access-denied');
    }

    // 加载页面特定的数据
    loadPageData(location.pathname);

  }, [location, user]);

  return null; // 这是一个工具组件,不渲染内容
}

导航状态管理

jsx
// 导航状态管理
function useNavigationState() {
  const [isNavigating, setIsNavigating] = useState(false);
  const [navigationError, setNavigationError] = useState(null);
  const navigate = useNavigate();

  const navigateWithLoading = useCallback(async (path, options = {}) => {
    setIsNavigating(true);
    setNavigationError(null);

    try {
      // 预加载路由数据(如果需要)
      if (options.preload) {
        await preloadRouteData(path);
      }

      navigate(path, options);

    } catch (error) {
      setNavigationError(error.message);
      console.error('Navigation failed:', error);
    } finally {
      setIsNavigating(false);
    }
  }, [navigate]);

  const navigateWithRetry = useCallback(async (path, options = {}, maxRetries = 3) => {
    for (let i = 0; i < maxRetries; i++) {
      try {
        await navigateWithLoading(path, options);
        return true;
      } catch (error) {
        if (i === maxRetries - 1) {
          setNavigationError('Navigation failed after multiple attempts');
          return false;
        }
        
        // 等待后重试
        await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
      }
    }
  }, [navigateWithLoading]);

  return {
    isNavigating,
    navigationError,
    navigateWithLoading,
    navigateWithRetry
  };
}

// 导航状态显示
function NavigationStatus() {
  const { isNavigating, navigationError } = useNavigationState();

  if (navigationError) {
    return (
      <div className="navigation-error">
        <p>Navigation failed: {navigationError}</p>
      </div>
    );
  }

  if (isNavigating) {
    return (
      <div className="navigation-loading">
        <p>Loading...</p>
      </div>
    );
  }

  return null;
}

实战应用

应用1:电商结账流程

jsx
function CheckoutFlow() {
  const navigate = useNavigate();
  const { cart, user } = useStore();
  const [checkoutData, setCheckoutData] = useState({
    shipping: null,
    payment: null,
    billing: null
  });

  const steps = [
    { path: 'shipping', title: 'Shipping Information', component: ShippingStep },
    { path: 'payment', title: 'Payment Method', component: PaymentStep },
    { path: 'review', title: 'Order Review', component: ReviewStep },
    { path: 'complete', title: 'Order Complete', component: CompleteStep }
  ];

  const proceedToNextStep = useCallback(async (currentStep, stepData) => {
    // 更新结账数据
    setCheckoutData(prev => ({ ...prev, [currentStep]: stepData }));

    switch (currentStep) {
      case 'shipping':
        // 验证配送信息后进入支付步骤
        if (await validateShippingInfo(stepData)) {
          navigate('/checkout/payment');
        } else {
          // 显示验证错误
          setValidationError('Invalid shipping information');
        }
        break;

      case 'payment':
        // 验证支付信息
        if (await validatePaymentMethod(stepData)) {
          navigate('/checkout/review');
        } else {
          setValidationError('Invalid payment information');
        }
        break;

      case 'review':
        // 提交订单
        try {
          const order = await submitOrder({
            ...checkoutData,
            items: cart.items
          });
          
          navigate('/checkout/complete', {
            state: { orderId: order.id },
            replace: true
          });
        } catch (error) {
          navigate('/checkout/error', {
            state: { error: error.message }
          });
        }
        break;
    }
  }, [navigate, checkoutData, cart]);

  return (
    <div className="checkout-flow">
      <CheckoutProgress steps={steps} />
      
      <div className="checkout-content">
        <Routes>
          {steps.map(step => (
            <Route
              key={step.path}
              path={step.path}
              element={
                <step.component
                  data={checkoutData[step.path]}
                  onProceed={(data) => proceedToNextStep(step.path, data)}
                  onBack={() => navigate(-1)}
                />
              }
            />
          ))}
          
          <Route path="error" element={<CheckoutError />} />
        </Routes>
      </div>
    </div>
  );
}

应用2:文档编辑器导航

jsx
function DocumentEditor() {
  const { documentId } = useParams();
  const navigate = useNavigate();
  const [document, setDocument] = useState(null);
  const [isDirty, setIsDirty] = useState(false);
  const [autoSaving, setAutoSaving] = useState(false);

  // 自动保存
  useEffect(() => {
    if (isDirty && !autoSaving) {
      const timer = setTimeout(async () => {
        setAutoSaving(true);
        try {
          await saveDocument(document);
          setIsDirty(false);
        } catch (error) {
          console.error('Auto-save failed:', error);
        } finally {
          setAutoSaving(false);
        }
      }, 2000);

      return () => clearTimeout(timer);
    }
  }, [isDirty, document, autoSaving]);

  const handleSaveAndNavigate = useCallback(async (targetPath) => {
    if (isDirty) {
      try {
        await saveDocument(document);
        setIsDirty(false);
        navigate(targetPath);
      } catch (error) {
        console.error('Save failed:', error);
        // 询问用户是否继续
        if (confirm('Failed to save document. Continue anyway?')) {
          navigate(targetPath);
        }
      }
    } else {
      navigate(targetPath);
    }
  }, [navigate, document, isDirty]);

  const handleNewDocument = () => {
    handleSaveAndNavigate('/documents/new');
  };

  const handleOpenDocument = (docId) => {
    handleSaveAndNavigate(`/documents/${docId}`);
  };

  const handleCloseDocument = () => {
    handleSaveAndNavigate('/documents');
  };

  const navigateWithConfirmation = useCallback((path, message) => {
    if (isDirty) {
      if (confirm(message || 'You have unsaved changes. Continue?')) {
        navigate(path);
      }
    } else {
      navigate(path);
    }
  }, [navigate, isDirty]);

  return (
    <div className="document-editor">
      <header className="editor-header">
        <div className="editor-nav">
          <button onClick={handleCloseDocument}>
            ← Back to Documents
          </button>
          
          <h1>{document?.title || 'Untitled Document'}</h1>
          
          <div className="editor-status">
            {autoSaving && <span>Auto-saving...</span>}
            {isDirty && !autoSaving && <span>Unsaved changes</span>}
            {!isDirty && !autoSaving && <span>All changes saved</span>}
          </div>
        </div>

        <div className="editor-actions">
          <button onClick={handleNewDocument}>
            New Document
          </button>
          
          <button onClick={() => navigateWithConfirmation(
            '/documents/templates',
            'Switch to templates? Unsaved changes will be lost.'
          )}>
            Templates
          </button>
          
          <button onClick={() => navigateWithConfirmation(
            '/settings',
            'Go to settings? Unsaved changes will be lost.'
          )}>
            Settings
          </button>
        </div>
      </header>

      <div className="editor-content">
        <DocumentEditorComponent
          document={document}
          onChange={(doc) => {
            setDocument(doc);
            setIsDirty(true);
          }}
        />
      </div>
    </div>
  );
}

总结

编程式导航是React Router的强大功能:

  1. useNavigate Hook:核心的编程式导航工具
  2. 导航选项:支持替换历史、传递状态等选项
  3. 条件导航:基于业务逻辑的智能导航
  4. 导航拦截:useBlocker和useBeforeUnload防止意外离开
  5. 历史管理:自定义历史记录管理和追踪
  6. 复杂场景:支持多步骤流程、向导等复杂导航需求

掌握编程式导航能够创建更加智能和用户友好的导航体验。