前言
useRevalidator
是 React Router v6.4+
引入的一个强大钩子,用于在数据路由(Data Router
)中手动触发路由数据的重新验证(revalidation)。
它在需要主动刷新数据而不改变路由位置
的场景中非常有用。
一、useRevalidator 核心用途
手动数据刷新:用户触发数据重新加载(如点击刷新按钮)
轮询机制:定期更新数据(如实时仪表盘)
乐观更新后同步:在本地状态变更后与服务器同步
外部事件响应:响应 WebSocket
消息或其他外部事件
二、useRevalidator 钩子返回对象
useRevalidator
返回一个包含两个属性的对象:
revalidate
:触发重新验证的函数state
:当前重新验证状态(idle 或 loading
)
三、useRevalidator 基本用法示例
import { useRevalidator } from 'react-router-dom';
function RefreshButton() {
const { revalidate, state } = useRevalidator();
return (
<button
onClick={() => revalidate()}
disabled={state === 'loading'}
>
{state === 'loading' ? '刷新中...' : '刷新数据'}
</button>
);
}
四、useRevalidator 实际应用场景
4.1、用户手动刷新数据
import { useRevalidator, useLoaderData } from 'react-router-dom';
function UserDashboard() {
const { users } = useLoaderData();
const { revalidate, state } = useRevalidator();
const [lastUpdated, setLastUpdated] = useState(new Date());
const handleRefresh = () => {
revalidate();
setLastUpdated(new Date());
};
return (
<div className="dashboard">
<div className="dashboard-header">
<h1>用户管理</h1>
<div className="controls">
<button
onClick={handleRefresh}
disabled={state === 'loading'}
className="refresh-btn"
>
<span className="icon">🔄</span>
{state === 'loading' ? '加载中...' : '刷新数据'}
</button>
<p className="update-time">
最后更新: {lastUpdated.toLocaleTimeString()}
</p>
</div>
</div>
<UserList users={users} loading={state === 'loading'} />
</div>
);
}
function UserList({ users, loading }) {
if (loading) {
return <div className="loading-indicator">加载用户数据...</div>;
}
return (
<ul className="user-list">
{users.map(user => (
<li key={user.id} className="user-card">
<div className="avatar">{user.name.charAt(0)}</div>
<div className="user-info">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
</li>
))}
</ul>
);
}
4.2、实时数据轮询
import { useEffect } from 'react';
import { useRevalidator } from 'react-router-dom';
function RealTimeStockTicker() {
const { revalidate } = useRevalidator();
// 每15秒自动刷新数据
useEffect(() => {
const intervalId = setInterval(() => {
revalidate();
}, 15000);
return () => clearInterval(intervalId);
}, [revalidate]);
return (
<div className="ticker">
{/* 股票数据展示 */}
</div>
);
}
// 完整示例:股票监控仪表盘
function StockDashboard() {
const { stocks } = useLoaderData();
const { revalidate, state } = useRevalidator();
return (
<div className="stock-dashboard">
<div className="dashboard-controls">
<h2>实时股票行情</h2>
<div className="auto-refresh">
<label>
<input
type="checkbox"
onChange={e => setAutoRefresh(e.target.checked)}
/>
自动刷新 (每15秒)
</label>
</div>
<button
onClick={() => revalidate()}
className="refresh-btn"
>
手动刷新
</button>
</div>
{state === 'loading' ? (
<div className="loading-overlay">
<div className="spinner"></div>
<p>更新行情数据...</p>
</div>
) : null}
<StockTable stocks={stocks} />
</div>
);
}
4.3、乐观更新后重新验证
import { useRevalidator } from 'react-router-dom';
function TodoItem({ todo }) {
const { revalidate } = useRevalidator();
const [isUpdating, setIsUpdating] = useState(false);
const toggleCompleted = async () => {
// 乐观更新:立即更新UI
setIsUpdating(true);
try {
// 发送API请求
await updateTodoStatus(todo.id, !todo.completed);
// 成功:重新验证数据
revalidate();
} catch (error) {
// 错误处理
console.error('更新失败:', error);
} finally {
setIsUpdating(false);
}
};
return (
<li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={toggleCompleted}
disabled={isUpdating}
/>
<span className="todo-text">{todo.text}</span>
{isUpdating && <span className="updating-indicator">更新中...</span>}
</li>
);
}
4.4、WebSocket 实时更新
import { useEffect } from 'react';
import { useRevalidator } from 'react-router-dom';
function ChatRoom() {
const { messages } = useLoaderData();
const { revalidate } = useRevalidator();
useEffect(() => {
const socket = new WebSocket('wss://example.com/chat');
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.type === 'NEW_MESSAGE') {
// 收到新消息时重新验证数据
revalidate();
}
});
return () => socket.close();
}, [revalidate]);
return (
<div className="chat-room">
<MessageList messages={messages} />
<MessageInput />
</div>
);
}
五、useRevalidator 高级用法:带错误处理的重试机制
import { useState } from 'react';
import { useRevalidator } from 'react-router-dom';
function DataPanel() {
const { data, error } = useLoaderData();
const { revalidate, state } = useRevalidator();
const [retryCount, setRetryCount] = useState(0);
const handleRetry = () => {
revalidate();
setRetryCount(prev => prev + 1);
};
if (error) {
return (
<div className="error-panel">
<div className="error-message">
<h3>数据加载失败</h3>
<p>{error.message}</p>
<p>尝试次数: {retryCount}</p>
</div>
<div className="error-actions">
<button
onClick={handleRetry}
disabled={state === 'loading'}
>
{state === 'loading' ? '重试中...' : '重试加载'}
</button>
{retryCount > 2 && (
<button onClick={() => window.location.reload()}>
刷新页面
</button>
)}
</div>
</div>
);
}
return <DataDisplay data={data} />;
}
六、useRevalidator 与相关API对比
方法 用途 特点
useRevalidator
: 用于手动重新验证路由数据; 具有不改变路由位置,仅刷新数据的特点
useNavigate
: 用于编程式导航; 具有改变路由位置
,触发新数据加载的特点
useFetcher
: 用于提交数据或加载数据; 具有不触发导航
,适合局部操作的特点
loader函数
: 用于路由数据加载; 具有自动执行
的路由数据获取的特点
七、最佳实践
1、用户反馈:在重新验证期间提供加载状态
2、防抖处理:避免频繁触发重新验证
3、错误处理:妥善处理重新验证失败情况
4、资源清理:清除轮询和事件监听器
5、性能优化:避免不必要的重新验证
// 带防抖的刷新按钮
function DebouncedRefreshButton() {
const { revalidate, state } = useRevalidator();
const debouncedRevalidate = useDebounce(revalidate, 300);
return (
<button
onClick={debouncedRevalidate}
disabled={state === 'loading'}
>
安全刷新
</button>
);
}
// 自定义防抖钩子
function useDebounce(callback, delay) {
const timerRef = useRef();
return (...args) => {
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
callback(...args);
}, delay);
};
}
八、注意事项
- 数据路由要求:必须在数据路由中使用(使用
createBrowserRouter
等) - 状态管理:
state 仅
表示重新验证状态,不表示初始加载 - 组件卸载:确保在组件卸载时清理轮询和事件监听
- 数据变化:重新验证会触发所有活动路由的
loader
重新执行 - 错误边界:重新验证中的错误会被路由错误边界捕获
总结
useRevalidator 是 React Router
数据路由模型中的关键工具,它解决了在不改变路由位置的情况下刷新数据的常见需求。通过合理使用此钩子,我们可以:
a、创建用户友好的数据刷新机制
b、实现实时数据更新功能
c、构建健壮的乐观更新流程
d、响应外部数据变化事件
e、提升应用的数据同步能力
此钩子特别适合需要保持UI与服务器数据同步的复杂应用场景,是构建现代数据驱动应用的强大工具。