为了帮助我们处理何时在客户端上统一增加前端的周计数器,您应该返回周何时增加的时间戳。
为了获得这个时间戳,我们创建了一个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()
});
})
});