-1

我有一个使用 Firebase 的 React 应用程序。为了显示每周数据,我使用此端点从 Cloud Functions 获取周数:

exports.getWeekNumberApi = functions.https.onRequest((req, res) => {
    cors(req, res, () => {
        res.setHeader('Content-Type', 'application/json');
        res.send(JSON.stringify({ week: getWeekNumber(new Date()) }));
    })
});

GetWeekNumber 返回周数。

现在我不想一直调用这个 api,但我不确定如何缓存和清除数据,这样它就不会在一周变化时运行。如果用户在周日晚上访问该应用程序,则应在周日午夜调用该 api 并清除缓存。知道如何在 React 中做到这一点吗?

我唯一能想到的就是设置一个cookie并计算距离周日午夜还有多长时间,然后再清除cookie。

4

1 回答 1

-1

为了帮助我们处理何时在客户端上统一增加前端的周计数器,您应该返回周何时增加的时间戳。

为了获得这个时间戳,我们创建了一个Date实例,将它的时间更改为午夜,然后将其设置为下周日。在您部署的 Cloud Functions 代码中,您可以使用以下函数实现此目的:

// Gets a Date object representing the next Sunday at 00:00
// Note: Functions run in the UTC timezone, so this code works
//       without UTC offset adjustments
getDateForFutureSundayAtMidnight() {
  const d = new Date();
  // set time to midnight (00:00)
  d.setHours(0,0,0,0);
  // set date to the next Sunday
  d.setDate(d.getDate() - d.getDay() + 7);
  return d;
}

然后你将你的getWeekNumberApi函数编辑为:

exports.getWeekNumberApi = functions.https.onRequest((req, res) => {
    cors(req, res, () => {
        const expiresDate = getDateForFutureSundayAtMidnight();

        res.json({
          week: getWeekNumber(new Date()),
          expires: expiresDate.getTime()
        });
    })
});

作为简化,如上所示,您可以使用response.json(/* object */)来代替Content-Type自己设置和字符串化对象。

在客户端,您可以将结果存储在localStorage. 在这里,我们检查本地缓存以查看是否存储了任何响应,如果没有,我们调用 API 并存储其响应。

// synchronous function, only queries cache
function getCachedResponse(id, fallback = null) {
  const cached = window.localStorage.getItem(id)
  if (cached !== null) {
    const cachedObject = JSON.parse(cached);
    if (Date.now() < cachedObject.expires)
      return cachedObject;
  }
  return fallback;
}

// asynchronous function, calls API when expired/not available
async function fetchAndCacheResponse(id, url) {
  const cached = window.localStorage.getItem(id);

  if (cached !== null) {
    const cachedObject = JSON.parse(cached);
    if (Date.now() < cachedObject.expires)
      return cachedObject;
  }

  // calls API
  const res = await fetch(url);

  if (!res.ok) {
    throw new Error(url + " returned unexpected status code: " + res.status);
  }

  const bodyString = res.text();
  window.localStorage.setItem(id, bodyString);

  return JSON.parse(bodyString);
}

// Usage:
// fetchCachedResponse("myApp:week", "https://us-central1-PROJECT-ID.cloudfunctions.net/getWeekNumberApi");

在您的组件/服务中,您可以使用useEffect调用来应用它。

// set initial week number to -1 while loading or use
// a cached value if available & valid
const [weekNumber, setWeekNumber] = useState(() => getCachedResponse("myApp:week", -1));
const [weekNumberLoading, setWeekNumberLoading] = useState(weekNumber !== -1);

useEffect(() => {
  let timerID, disposed = false;

  fetchCachedResponse("myApp:week", "https://us-central1-PROJECT-ID.cloudfunctions.net/getWeekNumberApi")
    .then((response) => {
      if (disposed) return; // component was unmounted/result is stale, do nothing

      const timeToNextIncrement = response.expires - Date.now();
      const weekNumber = response.week;

      // schedule the next update and update the state's week number
      // NOTE: This is for 1-based week numbers: 1-52
      //       Use `(weekNumber + 1) % 52` for 0-based week numbers
      timerID = setTimeout(() => setWeekNumber((weekNumber % 52) + 1), timeToNextIncrement);
      setWeekNumber(weekNumber);
      setWeekNumberLoading(false);
    })
    .catch((err) => {
      if (disposed) return; // component was unmounted/result is stale, do nothing
      // something went wrong with getting the week number
      console.error("failed to get week number", err);
      setWeekNumber(-1); // <- use what you want here (e.g. locally calculated number?)
      setWeekNumberLoading(false);
    });

  return () => {
    timerID && clearTimeout(timerID); // clear the timer, if set
    disposed = true;                  // ignore the result of any pending promises
  }
}, [weekNumber]); // rerun when weekNumber is changed

注意:使用第三方useAsyncEffect实现可以简化上面的代码。


为了进一步减少点击您的 Cloud Function 的机会,您可以使用 CDN 和浏览器缓存,使用Cache-Control标头及其相关项。为获得最佳结果,请在 Firebase 托管之后提供您的功能,以便您也可以使用其 CDN。

在下面的代码示例中,我们用于public告诉任何缓存来存储响应,immutable以指示响应在过期之前不会更改(在撰写本文时,这是一个新指令并且客户端支持有限)和一个Expires标头集到下一次出现Sunday 00:00

exports.getWeekNumberApi = functions.https.onRequest((req, res) => {
    cors(req, res, () => {
        const expiresDate = getDateForFutureSundayAtMidnight();

        res.setHeader('Cache-Control', 'public, immutable'); // cache in any CDN, won't change until it expires
        res.setHeader('Expires', expiresDate.toGMTString()); // when this entry expires
        res.json({
            week: getWeekNumber(new Date()),
            expires: expiresDate.getTime()
        });
    })
});
于 2021-06-28T18:16:43.103 回答