URL 作为唯一身份源
用 URL 参数(如 /consultation/:id)作为"当前资源是谁"的唯一判断依据,而不是在组件 state 里维护一份并行的"当前 ID"。
#type / concept
#status / growing
#tech / dev / frontend
#resource / react
[!info] related notes
- 前置: React Router 路由与权限
- 架构: 四层状态架构
- 派生: 派生状态模式
- 迁移: 从 useState 迁移到 TanStack Query
URL 作为唯一身份源
一句话定义
“当前看的是哪个资源” 应该只由 URL 决定,不应该同时存在 URL id 和组件 state 里的 currentXxx.id 两套身份源。
核心机制
问题:两套身份源并行
// URL: /consultation/abc-123
const { id } = useParams();
// 组件里还有一份"当前会话"
const [currentConversation, setCurrentConversation] = useState<Conversation | null>(null);
当删除全部会话后:
setCurrentConversation(null) → 身份源 A 被清空
navigate('/consultation') → 身份源 B 被清空
但这两个操作不是原子的。如果 setCurrentConversation(null) 先触发了 useEffect,而 URL 还是旧 id,就会用旧 id 重新请求已删除的会话。
解决:收敛到 URL
const routeConversationId = id && id !== 'new' ? id : null;
所有需要”当前会话是谁”的地方,都用 routeConversationId,不再用 currentConversation?.id。
数据跟着 URL 走
const conversationQuery = useQuery({
queryKey: ['consultation', 'conversation', routeConversationId],
queryFn: () => consultationApi.getConversation(routeConversationId!),
enabled: !!routeConversationId,
});
语义天然就是:
routeConversationId存在 → 请求routeConversationId变了 → 请求新 idrouteConversationId为空 → 不请求
删除后自然重置
navigate('/consultation', { replace: true });
// routeConversationId = null
// enabled = false
// 不请求旧会话
// currentConversation = null(从 query data 派生)
// messages = [](从 query data 派生)
不需要手动清空任何状态。
最小例子
// ❌ 两套身份源
const [activeId, setActiveId] = useState<string | null>(null);
// 删除时:setActiveId(null) + navigate(...) 可能竞态
// ✅ 只看 URL
const { id } = useParams();
const activeId = id && id !== 'new' ? id : null;
// 删除时:只 navigate(...),activeId 自然变 null
边界与易混淆点
- URL 是”身份源”,不是”数据源”。URL 说”我看的是 abc-123”,但 abc-123 的具体内容由 query cache 提供
- 侧边栏高亮应该用
routeConversationId,不应该依赖currentConversation?.id,因为后者可能还没加载出来 chatSessionKey也应该从routeConversationId派生,不应该单独维护一份 state