知行札记
专题技术实践Web 界面

路由、状态与请求结果

给 URL、服务器数据和本地交互各自明确的事实来源,处理键、取消、重试、失效与离线。

哪些状态应在什么位置

组件展开与临时输入可以由局部状态维护;跨组件的客户端交互可以由 Context 或外部 store 维护;可分享筛选、分页和排序适合 URL;服务器记录在客户端表现为缓存。每个位置说明主要事实和修改入口,避免同时保存多份需要同步的筛选条件。

路由解析路径和查询字符串,取得可识别参数,随后加载与呈现。URL 参数是外部输入,先解析并验证类型、范围和允许值。页面索引、页长和排序列需要明确默认、上限和白名单,不能直接把任意文本变成 SQL 字段。

TanStack Router、React Router 和框架路由各有生成、加载和历史规则。自动路由树由配置生成,导航与退回还需要核对焦点、滚动、错误和未保存输入。路由守卫可以改善体验,服务端对象授权仍在实际操作入口强制。

一个查询键表达哪份结果

服务器状态缓存的键应包括所有会改变结果的输入。相同资源的不同筛选、语言、主体或版本需要相应区分。登出、切换身份和权限变化需要清理或隔离数据,不能继续复用前一主体结果。

const query = useQuery({
  queryKey: ['items', { ownerId, page, filter }],
  queryFn: ({ signal }) => fetchItems({ ownerId, page, filter }, signal),
  staleTime: 30_000,
});

这是 TanStack Query 的接线示意:fetchItems 必须使用相同参数,消费 signal,并在边界解析响应。示例 30 秒是教学配置,需要按数据变化与产品目标选择。

staleTime 控制结果何时被视为陈旧,垃圾回收时间控制无订阅缓存保留;两者不保证服务端写入后立即新鲜。写成功后可以更新已知准确结果或使相关键失效,失效重取仍需要处理期间的可见状态。

查询函数要完成参数到请求、响应到可信结果的两次映射。以下与前面的键使用同一组输入;主体由服务端会话确认,ownerId 只是被查询的对象范围,不能成为授权凭证:

import { z } from 'zod';

const pageSchema = z.object({
  items: z.array(z.object({ id: z.string(), title: z.string() })),
  nextPage: z.number().int().positive().nullable(),
});
type Params = { ownerId: string; page: number; filter: string };

async function fetchItems(params: Params, signal: AbortSignal) {
  const url = new URL('/api/items', window.location.origin);
  url.searchParams.set('owner', params.ownerId);
  url.searchParams.set('page', String(params.page));
  url.searchParams.set('filter', params.filter);
  const res = await fetch(url, { signal, credentials: 'same-origin' });
  if (!res.ok) throw new Error(`Items request failed: ${res.status}`);
  const raw: unknown = await res.json();
  return pageSchema.parse(raw);
}

React 顶层用 QueryClientProvider 提供稳定的 QueryClient;普通 SPA 可在模块中创建一个,服务器渲染应按请求隔离客户端,避免跨请求共享私有缓存。错误策略根据可恢复性处理,示例的统一 Error 只展示 HTTP 拒绝与结构失败都会进入失败分支。

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { App } from './App';

const client = new QueryClient();
export function QueryApp() {
  return <QueryClientProvider client={client}><App /></QueryClientProvider>;
}

同一 SPA 在身份变化时调用明确的缓存清理入口。组件中的 useQuery 从该 provider 取得客户端;signal 进入 fetch 后,中止才传到网络操作。该示例假设 /api/items 接受这些查询字段并返回上述 schema,尚未运行端到端请求。

HTTP 客户端与缓存怎样分工

fetch、axios、ky、ofetch 执行请求及解析响应;查询层协调缓存与状态。fetch 收到 HTTP 错误状态仍可正常完成 Promise,需要检查 res.ok。JSON 解析成功也未证明结构符合契约。

取消信号只有传到支持它的操作才生效。TanStack Query 的取消与缓存行为取决于 query function 是否消费信号;组件卸载不能无条件推断网络请求已停止。Query Cancellation

重试应有一个清楚的责任者,避免客户端、查询层、代理和服务各自重试使次数相乘。读取和写入的风险不同,写入超时需根据幂等契约或查询结果决定下一步。

mutation 和乐观更新怎样恢复

动作入口记录提交输入与待处理状态,成功后以服务器接受结果更新事实,失败后显示可辨原因。禁用按钮减少同一界面的重复点击,不能防止其他客户端或网络重发。

乐观更新先显示预期结果,需要保存足以恢复的信息,同时处理在途读取和其他并发变化。简单回滚到旧快照可能覆盖较新的成功修改;多任务并发时,恢复应依据身份和版本。

外部 store 与离线

Zustand 等 store 通过订阅向 React 提供状态,选择器缩小读取范围;引用稳定性、相等规则和更新方式会影响通知。Context 适合共享明确范围的数据或接口,不要求所有数据都进全局 store。

持久化需要序列化格式与版本。浏览器存储可能满额、拒绝或被清理,恢复缓存不代表未同步操作已经完成。离线写入应保留操作身份、输入、进度和重复处理政策;普通请求缓存持久化不会自动提供冲突合并。

实时通知可以触发失效或更新,消息仍可能重复、乱序或缺失。连接恢复后根据版本或进度补齐,不能只接收以后新事件。具体文档协同数据规则由相应编辑实践完整维护。

最后更新于

本页目录