优化 Web 组件中的 API 调用
概述
概述
构建 Web 组件时,您可能需要从外部实体获取资源。这通常被称为“API 调用”。在本篇博文中,我将探讨如何使用 JavaScript 的fetch方法以及一些其他技巧来优化 API 调用,从而获取数据。本文中,我将引用我为elmsln/lrnwebcomponentsWeb 组件 mono-repo 编写的一个元素中的代码。
该元素的完整源代码位于github-preview-source。
要点
浏览器如何加载 JavaScript
这看起来似乎是个简单的概念:你把脚本添加到 HTML 页面中,JavaScript 就加载完毕了。但实际上,浏览器做的远不止加载脚本这么简单。JavaScript 基于异步处理的概念,也就是说,浏览器在执行页面设置等其他操作的同时,也会处理 JavaScript 代码。这一切都发生得非常迅速,但都在浏览器的事件循环中完成。
事件循环会将任务放入队列中,执行每个任务并等待其完成,然后再执行队列中的下一个任务。理解这一点很重要,因为我们的 API 调用会被注册为一个任务,并由于我们将其封装在 `setTimeout` 调用中而排在脚本后面。稍后会详细介绍……
使用 fetch 发起 API 请求
这或许很简单,但我还是要讲解一下。在我的 Web 组件中,我会定义一个名为 `getData()` 的函数fetchGithubData。这个函数会接收一些参数,用于调用 GitHub 的 API,并将数据返回给一个处理方法。处理方法会将数据保存到我们的元素中,以便显示出来。
fetchGithubData(apiUrl, org, repo){
fetch(`${apiUrl}/repos/${org}/${repo}`)
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.handleResponse(json);
})
.catch((error) => {
this.__assetAvailable = false;
console.error(error);
});
}
该函数接收一个 apiUrl(https://api.github.com)、一个 GitHub 组织或用户以及一个存储库名称,然后将该数据提供给我们的处理函数。
handleResponse(response) {
if (response) {
this.__assetAvailable = true;
this.__description = response.description;
this.repoLang = response.language;
this.__stars = response.stargazers_count;
this.__forks = response.forks;
}
}
我们的数据处理程序首先检查是否收到响应,如果收到响应,它会设置一些属性,这些属性会在我们的 Web 组件中呈现。
以下是我们的 Web 组件示例,供您参考。 可以看到,它包含了我们在处理方法中设置的一些属性,例如标题、仓库的主要语言、分支数、描述和星标数。
发光元素生命周期方法
由于我的元素使用了lit-element库,我们将利用生命周期方法来发起API调用。lit-element提供了几个生命周期方法,但我们将重点关注其中的两个:`on`firstUpdated和`on` updated。
该firstUpdated方法会在 DOM 注册元素后立即被调用。updated生命周期方法紧随firstUpdated其后,我们将在此处发起 API 调用。
我们希望在更新函数中调用 API,因为如果元素挂载并渲染后仓库或组织发生变化,我们可以响应这种变化,因为我们的元素已经“更新”了。请观看此演示视频,了解为什么我们在更新生命周期方法中使用 API 调用。
如果您有兴趣了解更多关于Lit-Elements生命周期方法的信息,请访问他们的文档:https://lit-element.polymer-project.org/guide/lifecycle
使用超时和防抖
现在我将展示为什么以及如何使用 Javascript 内置setTimeout函数来进行 API 调用。
以下是我们更新后的生命周期方法中的代码。
updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
// only make the fetch after we get everything setup
if (
[
"repo",
"org",
].includes(propName) &&
this[propName]
) {
clearTimeout(this.__debounce);
this.__debounce = setTimeout(() => {
this.fetchGithubData(
this.apiUrl,
this.repo,
this.org,
);
}, 0);
}
});
}
我们使用 forEach 循环遍历每个已更改的属性。您可能会问,那么初始设置的属性怎么办?这些属性仍然被视为新属性,并在组件挂载时传递给更新函数。
接下来,我们检查要考虑的属性是否属于该类。如果防抖变量中已经设置了超时时间,则将其清除。这样做是为了确保 API 调用只进行一次,这样当 forEach 循环遍历到最后一个已更改的属性时,超时时间就不会被清除,从而避免再次进行 API 调用。
我们使用 `setTimeout` 是因为浏览器会在文件中所有 JavaScript 代码处理完毕后调用它。这样可以确保在发起 API 调用之前一切就绪。`setTimeout` 回调函数会被添加到浏览器的事件循环队列中,并在文件中所有其他 JavaScript 代码解析完毕后立即被调用。
应用缓存标头
最后,我们会在请求中添加头部信息,告诉浏览器缓存我们的结果(以便稍后使用)。这样可以提高性能。当浏览器再次发出相同的请求时,它会先检查缓存,如果缓存中已有响应对象,则会使用缓存的响应,而不是发出新的请求。
我们可以在元素的构造函数中设置头部信息来实现这一点,如下所示:
constructor() {
super();
this.url = "https://github.com";
this.apiUrl = "https://api.github.com";
this.rawUrl = "https://raw.githubusercontent.com";
this.extended = false;
this.readMe = "README.md";
this.branch = "master";
this.viewMoreText = "View More";
this.notFoundText = "Asset not found";
this.headers = {
cache: "force-cache",
};
}
然后我们可以在 fetch 调用中使用这些标头。
fetch('https://someendpoint.com/git/', this.headers)
结论
就是这样!如果您有兴趣了解更多关于 Web 组件以及我在本文中提到的一些内容,请查看下面的资源部分。