URL 作为唯一身份源

用 URL 参数(如 /consultation/:id)作为"当前资源是谁"的唯一判断依据,而不是在组件 state 里维护一份并行的"当前 ID"。

#type / concept #status / growing #tech / dev / frontend #resource / react

[!info] related notes

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 变了 → 请求新 id
  • routeConversationId 为空 → 不请求

删除后自然重置

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
创建于 2026/7/2 更新于 2026/7/15