我有一个使用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中如何做到这一点吗?
我唯一能想到的就是放一块饼干,计算周日午夜前的时间,然后稍微清空饼干。
为了帮助我们处理何时在客户端之间统一增加前端的周计数器,您应该返回周何时增加的时间戳。
为了获得这个时间戳,我们创建了一个Date
实例,将其时间更改为午夜,然后将其设置为下周日。在您部署的云功能代码中,您可以使用以下功能来实现这一点:
// 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
中,而不是使用cookie。在这里,我们检查本地缓存以查看是否存储了任何响应,如果没有,我们调用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
实现可以简化上述代码。
为了进一步减少访问云功能的机会,您可以使用Cache-Control
标头及其亲属来使用CDN和浏览器缓存。为了获得最佳效果,请在Firebase Hosting背后服务您的功能,以便您也可以使用其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()
});
})
});