通往 PWA 之路——第三部分
故事仍在继续……
这篇文章最初发布在我的个人博客上。
故事仍在继续……
既然我们已经了解了服务人员的能力,现在是时候真正利用他们了。
我们将用它来做什么呢?*隆重介绍*缓存!
缓存可以让我们的应用程序即使在用户离线时也能运行,
这是渐进式 Web 应用程序的关键特性之一。
所以,我们将在本文中讨论以下内容:
- 缓存和缓存版本控制
- 预缓存
- 动态缓存
- 缓存实用程序
缓存和缓存版本控制
缓存是用于存储请求及其对应响应的键值对存储。
它们允许我们预先缓存静态数据以供后续使用,或者动态添加内容,以便将其保存下来供离线使用。
缓存需要手动管理,因此数据不会自动添加或更新。
缓存也没有过期时间,我们需要清除过期数据时,可以手动删除单个数据,也可以直接删除整个缓存。
由于我们需要手动维护缓存,因此也必须确保缓存提供的数据是最新的。
在继续之前,让我们先来看看如何打开缓存:
caches.open($cacheName).then(cache => {});
打开缓存时,我们需要提供一个缓存名称。如果已存在同名缓存,则会打开该缓存;否则,将创建一个新的同名缓存对象。
caches.open(...)返回一个Promise解析为已打开缓存的对象,因此我们可以在一个.then(cache => {})块中修改缓存。
由于缓存是通过名称进行操作的,因此很容易因为缓存名称错误而导致应用程序出错。所以显而易见的解决方案是将正在使用的缓存集中存储和管理。
const currentCaches = {
static: "static-cache-v1",
dynamic: "dynamic-cache-v1"
};
上面的代码片段还展示了如何对缓存应用版本控制。
缓存的名称由其类型组成,在本例中,我们处理一个静态缓存和一个动态缓存,以及一个版本字符串,在本例中为“” v1。
因此,每当我们更改静态缓存中的数据时,我们都必须更新缓存版本,以确保更新后的数据也在缓存中得到更新。
缓存清理
正如我在上一篇文章中提到的,服务工作线程生命周期的激活阶段非常适合清除过时的缓存。
self.onactivate = event => {
const KNOWN_CACHES = Object.values(currentCaches);
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (KNOWN_CACHES.indexOf(cacheName) < 0) {
console.log("Purging outdated cache:", cacheName);
return caches.delete(cacheName);
}
})
);
})
);
};
我们activate通过调用函数来延长事件持续时间event.waitUntil(...),并检查每个可用的缓存是否在已知缓存列表中。如果不在列表中,我们会将其删除,因为它不再需要了。
由于caches.delete(...)返回的是一个Promise,我们将清理代码包装在一个中Promise.all(...),它接受一个对象列表Promise,并且只有当这些Promise对象中的每一个都解析时才会解析。
预缓存
顾名思义,预缓存是指在实际需要数据之前就将其存储起来。
在 PWA 中,预缓存通常用于存储正确显示应用程序静态“外壳”所需的资源,
例如:
- CSS
- JS
- 图片
- 字体
- ETC。
缓存提供应用程序基本版本所需的静态资源通常被称为“应用程序外壳”策略。
应用程序外壳缓存发生在install服务工作线程阶段,即使用户离线,我们也可以显示应用程序的静态内容。
self.oninstall = event => {
const dataToCache = [
"/app-icon-48x48.6dc6b62a.png",
"/apple-icon-76x76.3b41636a.png",
"/main-image.8ec44c4f.jpg",
"/main-image-lg.8b45ce89.jpg",
"/manifest.f43e1207.webmanifest",
.
.
.
];
event.waitUntil(
caches.open(currentCaches.static).then(cache => {
cache
.addAll(dataToCache)
.catch(error =>
console.log("Failed to initialize static cache:", error)
);
})
);
};
cache.add(...)它接受一个 URL 作为参数,获取该 URL 并将生成的请求/响应对放入当前打开的缓存中。
它的扩展程序cache.addAll(...)工作方式完全相同,但它处理的不是单个 URL,而是整个 URL 列表。cache.addAll(...)它提供了一种简洁高效的方式,可以将静态资源列表添加到缓存中。另一方面,如果某个资源获取失败,此调用将导致缓存未完全初始化。
动态缓存
好的,现在即使用户离线,我们也能够显示应用程序的静态框架。但是如果我们还想在离线模式下显示动态数据呢?
安装时我们无法获知任何动态数据,例如用户图片、文本帖子等,因此无法将其放入静态缓存。幸运的是,我们还可以拦截来自应用程序的任何请求。
通过监听该fetch事件,我们可以拦截并分析任何请求和/或响应。这是执行动态缓存的理想场所。
self.onfetch = event => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
} else {
return fetch(event.request)
.then(fetchedResponse => {
if (!fetchedResponse.ok) {
return fetchedResponse;
} else {
return caches
.open(currentCaches.dynamic)
.then(cache => {
if (event.request.method === "GET") {
const clonedResponse = fetchedResponse.clone();
cache.put(event.request, clonedResponse);
}
return fetchedResponse;
})
.catch(reason =>
console.log("An error occured while caching data:", reason)
);
}
})
.catch(reason => {
console.log("An error occured while fetching data:", reason);
});
}
})
);
};
这段动态缓存代码片段应用了所谓的“缓存优先”策略。
对于应用程序发出的每个请求,我们首先检查是否已存在缓存响应。如果存在,则立即返回缓存的响应。这最终会加快响应速度,但也可能导致数据过时。
如果缓存未命中,我们会检查fetch初始请求,判断请求是否成功,并将请求/响应对添加到缓存中。虽然也可以使用已知的方法
来实现,但如果您想为缓存添加额外的自定义逻辑,以下是一个更详细的入门指南。cache.add(...)cache.addAll(...)
需要注意的一点是调用 `getResponse()` 方法fetchedResponse.clone()。
由于响应是流,因此只能被使用一次。所以,为了在将获取到的响应添加到缓存后返回它,我们必须创建一个副本。
缓存实用程序
缓存是一个非常复杂的话题。缓存策略有很多种,具体采用哪种策略取决于实际情况。
Mozilla 提供了一本所谓的“服务工作线程手册”,其中包含有关各种缓存策略的更多详细信息。
此时你可能还会问自己,每次需要预缓存和/或动态缓存时,我们是否都必须重新发明轮子。
答案是:否。
Google 提供了一个名为Workbox 的工具,它可以帮助您编写样板代码,例如预缓存、动态缓存等,这样您就不必手动编写缓存代码了。
结论
在这篇文章中,我演示了如何进行以下操作
- 缓存版本控制
- 缓存清理
- 预缓存
- 动态缓存
下一篇博文我们将探讨如何在 IndexedDB 中存储动态内容,敬请期待!
这么久
西蒙
文章来源:https://dev.to/s1hofmann/the-road-to-pwa---part-3-398j