R42第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

数据请求与缓存策略

数据请求与缓存策略 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完你能实现一个带缓存的最小请求方案:缓存键跟随筛选条件、命中即秒开、未命中带加载态、失败可重试、写入后主动失效;并用来回切换验证「不再闪烁,结果始终与参数对应」。

问题场景

课程库按分类浏览:每次点分类都重新请求,来回切换白屏闪烁;网络抖动一次,页面就停在空状态;改完课程标题回到列表,列表还是旧的。第一版只有一个共享的 isLoading——根本说不清「现在显示的是哪个分类的哪次结果」。

核心解释

最小可用方案 = 以参数为键的缓存 + 每键独立状态机:

const cache = new Map(); // key → { ts, data }

function useCourses(key) {
  const [state, setState] = useState(() => cache.get(key) || { data: null });
  const [attempt, setAttempt] = useState(0); // 重试计数

  useEffect(() => {
    let alive = true;
    const cached = cache.get(key);
    setState(cached ? cached : { data: null }); // 命中:旧数据先行;未命中:加载态
    fetchCourses(key)
      .then((entry) => {
        cache.set(key, entry);                  // 缓存只存成功结果
        if (alive) setState(entry);
      })
      .catch(() => {
        if (alive) setState((s) => ({ ...s, error: true }));
      });
    return () => { alive = false; };            // 竞态守卫
  }, [key, attempt]);

  return { ...state, retry: () => setAttempt((a) => a + 1) };
}

缓存键的生成收口成一个函数,参数增减只改一处:

const makeKey = (p) => [p.category, p.page, p.keyword].filter(Boolean).join("|");

四个要点对应四种体验:缓存键必须包含全部影响结果的参数(分类、页码、关键词——漏一个,就会出现「A 分类的数据配 B 分类的标题」,而且因为命中缓存连请求都不发);命中先展示旧数据、后台刷新,来回切换不闪;失败是按键记录的状态,缓存里永远只有成功结果,重试只影响当前键;写操作后主动失效(删除课程后删掉列表缓存或直接更新它),下次读取自然重新请求。验证方式:两个分类来回切换各两次,第二次起应即时显示且数据正确。

示例拆解

实验区课程库有三个分类,请求延迟 500-900ms 不等:

  • 首次进入某分类显示「加载中…」;回访直接显示缓存并标注「缓存时间(旧数据先行)」;
  • 「断网模式」开关:请求必失败,显示错误态与「重试」按钮;关闭断网后点重试即成功;
  • 「强制刷新」按钮:删掉当前键的缓存重新请求,期间旧数据仍然显示;
  • 底部实时显示 cache Map 里现存哪些键;
  • 验证:切换 全部 → 入门 → 全部 → 进阶 → 全部,确认「全部」的两次回访都即时且正确;断网失败后再恢复网络重试成功。

动手任务

打开本课实验区:

  1. 给缓存加 30 秒过期:读取时检查 ts,超时当作未命中;验证过期后回访会重新请求。
  2. 把「强制刷新」改成「失效所有键」,观察底部键列表被清空、各分类下次进入都走加载态。
  3. 给 makeKey 思路加页码:切换「第 1 页 / 第 2 页」,确认两页各自有缓存、来回切换不闪。

常见错误

  • 缓存键漏参数:参数变了键没变,显示错误数据且连请求都不发——最难排查的一类 bug。
  • 所有请求共享一个 isLoading:无法表达「A 在加载、B 已就绪」;状态必须按键隔离。
  • 失败覆盖了缓存条目:恢复网络后一直显示失败;错误态放 state,缓存只存成功结果。
  • 写操作后忘记失效:新增/删除后列表还是旧的;写入成功后删除相关键或用响应更新缓存。

检查与复盘

自查清单:

  • 能说出缓存键里必须有哪些参数,为什么少一个就出错
  • 切换不闪、失败可重试、写后失效,三条都验证过
  • 缓存只存成功结果,错误态与数据分离

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选中等

手写一个请求缓存:useArticle(id) 想做到「同一 id 的重复请求只发一次,不同 id 各自缓存」。下列 key 设计正确的是?

Q2读代码较难

阅读代码(React 18+)。首次进入文章 1,2 秒后标题出现;回到列表页(组件卸载),再次进入文章 1。第二次进入时界面显示什么?

const cache = new Map();

function useArticle(id) {
  const [state, setState] = useState(
    cache.has(id)
      ? { status: 'done', data: cache.get(id) }
      : { status: 'loading', data: null }
  );

  useEffect(() => {
    if (cache.has(id)) return;
    let ignore = false;
    fetch('/api/article/' + id)
      .then((r) => r.json())
      .then((data) => {
        cache.set(id, data);
        if (!ignore) setState({ status: 'done', data });
      });
    return () => {
      ignore = true;
    };
  }, [id]);

  return state;
}

function ArticlePage({ id }) {
  const article = useArticle(id);
  return article.status === 'loading'
    ? <p>加载中…</p>
    : <h1>{article.data.title}</h1>;
}

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...