停止捕获 React 状态:将你的过滤器同步到 URL 🔗
Source: Dev.to

“不可共享”的仪表板问题
想象一下常见的 B2B SaaS 场景:一位高管打开了你的分析仪表板。他们花了三分钟配置数据——将状态过滤为 “Active”,将日期范围设为 “Last 30 Days”,按 “Highest Revenue” 对表格进行排序,并跳转到第 4 页。他们复制了 URL 并通过 Slack 发给团队负责人。
团队负责人点击链接,却看到默认的、未过滤的仪表板,而不是第 4 页的活跃高收入客户。上下文完全丢失。为什么会这样?因为原始开发者把所有这些过滤条件都封装在 React 的 useState 钩子里。当页面为团队负责人重新加载时,那些本地状态就消失了。
解决方案:URL 是唯一真实来源
为了在 Smart Tech Devs 构建企业级前端体验,我们遵循一条严格的规则:如果某个状态会影响屏幕上显示的数据,它必须存放在 URL 中。
通过将过滤器、排序和分页同步到 URL 搜索参数(查询字符串),我们实现了可深度链接、可分享且刷新后仍保持状态的仪表盘。
在 Next.js(App Router)中构建 URL 状态
我们不再使用 setFilter(),而是通过 Next.js 的钩子操作浏览器的 History API。下面展示如何构建一个安全更新 URL 的过滤下拉框。
// app/components/StatusFilter.tsx
"use client";
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
export default function StatusFilter() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
// Read the CURRENT state directly from the URL, defaulting to 'all'
const currentStatus = searchParams.get('status') || 'all';
const handleFilterChange = (newStatus: string) => {
// 1. Create a fresh URLSearchParams object based on current URL
const params = new URLSearchParams(searchParams.toString());
// 2. Set the new parameter (or delete it if resetting)
if (newStatus === 'all') {
params.delete('status');
} else {
params.set('status', newStatus);
}
// 3. Reset pagination to page 1 whenever a filter changes!
params.delete('page');
// 4. Update the URL without triggering a full page reload
router.push(`${pathname}?${params.toString()}`);
};
return (
handleFilterChange(e.target.value)}
className="filter-dropdown"
>
All Statuses
Active Only
Archived
);
}
在服务器组件中使用 URL 状态
由于状态已经存放在 URL 中,我们的 Next.js 服务器组件可以在首次请求时立即读取它。这意味着我们在服务器端获取已精准过滤的数据,从而消除加载指示器并实现极佳的 SEO 效果。
// app/dashboard/page.tsx
import { fetchClients } from '@/lib/db';
import StatusFilter from './components/StatusFilter';
// Next.js automatically passes searchParams to page components
export default async function DashboardPage({ searchParams }: { searchParams: { status?: string } }) {
const currentStatus = searchParams.status || 'all';
// Fetch directly from the DB using the URL state
const clients = await fetchClients({ status: currentStatus });
return (
客户名单
);
}
Conclusion
本地状态(useState)应仅用于瞬时的 UI 元素,例如打开模态框或在文本字段中输入。对于其他所有情况——过滤器、标签页、搜索查询和分页——URL 必须是唯一的真实数据来源。这是业余项目与专业协作 SaaS 平台之间的分界线。