React 编程式导航
下面,我们来系统的梳理关于 React Router 编程式导航 的基本知识点:
一、编程式导航概述
1.1 什么是编程式导航
编程式导航是通过JavaScript代码而非用户点击链接的方式控制路由跳转的技术。与声明式导航(使用<Link>
组件)不同,编程式导航在事件处理、异步操作或条件逻辑中触发导航。
核心特点:
- 在JavaScript逻辑中触发导航
- 适用于表单提交、API响应、用户交互后
- 支持传递状态数据
- 提供更多导航控制选项
1.2 编程式导航 vs 声明式导航
特性 | 编程式导航 | 声明式导航 |
---|---|---|
触发方式 | JavaScript函数 | 用户点击链接 |
使用场景 | 复杂逻辑、异步操作 | 简单导航 |
状态传递 | 支持复杂对象 | 仅支持URL参数 |
控制能力 | 高级(重定向、拦截等) | 基础 |
典型API | useNavigate() | <Link> 组件 |
二、核心API:useNavigate
2.1 useNavigate基础用法
useNavigate
是React Router v6提供的核心导航钩子:
import { useNavigate } from 'react-router-dom';function LoginButton() {const navigate = useNavigate();const handleLogin = () => {// 执行登录逻辑...navigate('/dashboard'); // 导航到仪表盘};return (<button onClick={handleLogin}>登录</button>);
}
2.2 导航选项参数
navigate
函数支持第二个参数用于配置导航行为:
navigate(targetPath, {replace: boolean, // 替换当前历史记录state: object, // 传递状态数据preventScrollReset: boolean, // 防止滚动重置relative: 'route' | 'path' // 相对导航模式
});
三、导航操作类型
3.1 Push导航(默认)
添加新条目到历史记录堆栈:
navigate('/products'); // 默认push导航
3.2 Replace导航
替换当前历史记录,不创建新条目:
navigate('/confirmation', { replace: true });
适用场景:
- 登录后重定向
- 表单提交成功页
- 不希望用户返回的页面
3.3 历史记录操作
模拟浏览器前进后退按钮:
const navigate = useNavigate();// 后退
navigate(-1); // 前进
navigate(1);// 后退两页
navigate(-2);
四、状态传递与获取
4.1 传递状态数据
在导航时传递附加数据:
navigate('/user/123', {state: {from: '/dashboard',timestamp: Date.now(),userData: { name: 'John' }}
});
4.2 获取导航状态
使用useLocation
获取传递的状态:
import { useLocation } from 'react-router-dom';function UserProfile() {const location = useLocation();const state = location.state;return (<div>{state?.from && <p>来自: {state.from}</p>}</div>);
}
五、相对导航
5.1 相对路径导航
基于当前URL进行相对导航:
function ProductActions({ productId }) {const navigate = useNavigate();// 相对于当前路径const goToEdit = () => navigate(`edit`, { relative: 'path' });// 相对于父路由const goToSimilar = () => navigate(`../similar/${productId}`);return (<div className="actions"><button onClick={goToEdit}>编辑</button><button onClick={goToSimilar}>类似产品</button></div>);
}
5.2 相对导航模式
模式 | 说明 | 示例 |
---|---|---|
path | 相对于当前路径 | /products/123 → edit = /products/123/edit |
route | 相对于父路由 | /products/123 → ../details = /products/details |
六、导航拦截
6.1 使用Prompt组件
阻止用户在未保存更改时离开:
import { Prompt } from 'react-router-dom';function EditForm() {const [isDirty, setIsDirty] = useState(false);return (<><Prompt when={isDirty}message="您有未保存的更改,确定要离开吗?" /><form>{/* 表单字段 */}</form></>);
}
6.2 自定义导航拦截
使用useBlocker
(需要react-router v6.4+):
import { unstable_useBlocker as useBlocker } from 'react-router-dom';function BlockNavigation() {const [isBlocking, setIsBlocking] = useState(false);useBlocker(() => {return window.confirm('确定要离开吗?');}, isBlocking);return (// 组件内容);
}
七、高级导航模式
7.1 导航加载状态
在导航期间显示加载指示器:
import { useNavigation } from 'react-router-dom';function Layout() {const navigation = useNavigation();return (<div>{navigation.state === 'loading' && <GlobalSpinner />}<Outlet /></div>);
}
7.2 导航后滚动控制
导航后滚动到特定位置:
navigate('/section', {state: { scrollTo: 'section-3' }
});// 在目标组件中
useEffect(() => {const element = document.getElementById(location.state?.scrollTo);if (element) {element.scrollIntoView({ behavior: 'smooth' });}
}, []);
八、常见使用场景
8.1 表单提交后导航
function OrderForm() {const navigate = useNavigate();const handleSubmit = async (e) => {e.preventDefault();try {const response = await fetch('/api/orders', {method: 'POST',body: JSON.stringify(formData)});if (response.ok) {navigate('/confirmation', {replace: true,state: { orderId: response.data.id }});}} catch (error) {console.error('提交失败:', error);}};return (<form onSubmit={handleSubmit}>{/* 表单内容 */}</form>);
}
8.2 身份验证重定向
function PrivateRoute({ children }) {const { isAuthenticated } = useAuth();const navigate = useNavigate();const location = useLocation();useEffect(() => {if (!isAuthenticated) {navigate('/login', {replace: true,state: { from: location.pathname }});}}, [isAuthenticated, navigate, location]);return isAuthenticated ? children : null;
}
8.3 搜索查询导航
function SearchBar() {const [query, setQuery] = useState('');const navigate = useNavigate();const handleSearch = () => {navigate(`/search?q=${encodeURIComponent(query)}`);};return (<div className="search-container"><input type="text"value={query}onChange={(e) => setQuery(e.target.value)}onKeyDown={(e) => e.key === 'Enter' && handleSearch()}/><button onClick={handleSearch}>搜索</button></div>);
}
九、实践与错误处理
9.1 编程式导航最佳实践
- 避免在渲染中导航:在useEffect或事件处理中导航
- 清理导航操作:在组件卸载时取消异步导航
- 合理使用replace:防止创建多余的历史记录
- 添加导航反馈:在长时间导航时显示加载状态
- 使用绝对路径:确保导航路径正确
9.2 常见错误处理
无限重定向循环:
useEffect(() => {if (!user) {navigate('/login', { state: { from: location.pathname } });}
}, [user, navigate, location]);
解决方案: 在登录页面检查来源路径是否已经是登录页
状态丢失问题:
// 错误:直接使用history.replace
navigate('/new', { replace: true, state: null }); // 会清除状态// 正确:保留现有状态
navigate('/new', { replace: true,state: location.state // 保留现有状态
});
十、示例:电商结账流程
function CheckoutFlow() {const navigate = useNavigate();const [step, setStep] = useState(1);const nextStep = () => {if (step < 3) {setStep(step + 1);navigate(`/checkout/step${step + 1}`);} else {completeCheckout();}};const prevStep = () => {if (step > 1) {setStep(step - 1);navigate(`/checkout/step${step - 1}`);}};const completeCheckout = async () => {try {const response = await submitOrder();navigate('/checkout/complete', {replace: true,state: { orderId: response.id,items: response.items }});} catch (error) {navigate('/checkout/error', {state: { error: error.message,step: step }});}};return (<div className="checkout-container"><div className="progress-bar"><div className={`step ${step >= 1 ? 'active' : ''}`}>1. 购物车</div><div className={`step ${step >= 2 ? 'active' : ''}`}>2. 配送信息</div><div className={`step ${step >= 3 ? 'active' : ''}`}>3. 支付</div><div className={`step ${step >= 4 ? 'active' : ''}`}>4. 完成</div></div><div className="step-content">{step === 1 && <CartStep onNext={nextStep} />}{step === 2 && <ShippingStep onNext={nextStep} onBack={prevStep} />}{step === 3 && <PaymentStep onNext={nextStep} onBack={prevStep} />}</div></div>);
}function OrderComplete() {const location = useLocation();const { orderId, items } = location.state || {};const navigate = useNavigate();if (!orderId) {return (<div className="error"><h2>订单信息缺失</h2><button onClick={() => navigate('/')}>返回首页</button></div>);}return (<div className="order-complete"><h2>订单创建成功!</h2><p>订单号: #{orderId}</p><div className="order-summary"><h3>订单明细</h3><ul>{items.map(item => (<li key={item.id}>{item.name} - ${item.price} × {item.quantity}</li>))}</ul></div><div className="actions"><button onClick={() => navigate('/products')}>继续购物</button><button onClick={() => navigate(`/orders/${orderId}`)}>查看订单详情</button></div></div>);
}
十一、总结
11.1 编程式导航核心要点
- useNavigate是核心:替代了旧版的useHistory
- 导航类型选择:push(默认) vs replace
- 状态传递:使用state选项传递复杂数据
- 相对导航:简化嵌套路由中的导航
- 导航拦截:保护用户未保存数据
11.2 性能优化建议
- 避免过度导航:减少不必要的路由跳转
- 使用懒加载:配合React.lazy拆分路由代码
- 预加载路由:在用户交互前预加载目标路由资源
- 取消导航请求:在组件卸载时中止导航相关请求