如何让多个Web Component实例仅单次请求HTML模板文件?
实现Web Component模板的单次请求缓存
问题背景
我定义了如下Web Component:
// web-component.js export class WebComponent extends HTMLElement { template = '/path/to/template.html'; tmpl = {}; constructor() { super(); } async connectedCallback() { const html = fetch(this.template).then(response => response.text()); this.doSomething(await html); } doSomething(html) { console.log(html); } } document.addEventListener('DOMContentLoaded', customElements.define('web-component', WebComponent));
对应的模板文件template.html:
<template id="web-component"> <h1>Text Goes Here</h1> </template>
在index.html中引入该组件并创建三个实例:
.... <head> <script type="module" src="/path/to/web-component.js"></script> </head> <body> <web-component>Foo</web-component> <web-component>Bar</web-component> <web-component>Baz</web-component> </body> ....
当前浏览器会对同一模板文件发起三次HTTP请求,我希望在客户端缓存模板内容,仅发起单次请求。
我尝试了两种实现方式,但均未成功:
第一种:
async connectedCallback() { const existing_template = document.body.querySelector('#web-component'); console.log(existing_template); if (existing_template) { this.doSomething(existing_template); } else { const html = fetch(this.template).then(response => response.text()); this.addTemplateToWebPage(await html); this.doSomething(await html); } addTemplateToWebPage(html) { const tag = document.createElement('body'); tag.innerHTML = html; document.body.appendChild(tag.querySelector('template')); }
第二种:
connectedCallback() { this.doSomething(); } async doSomething() { const existing_template = document.body.querySelector('#web-component'); console.log(existing_template); if (existing_template) { this.doSomethingElse(existing_template); } else { const html = fetch(this.template).then(response => response.text()); this.addTemplateToWebPage(await html); this.doSomethingElse(await html); } } doSomethingElse(html) { console.log('doing something else'); }
两种方式中existing_template始终为null,仍会发起重复请求,请问如何实现仅单次请求模板文件?
解决方案:利用类静态属性缓存模板Promise
你之前的方法失败的核心原因有两点:
- 多个组件实例几乎同时触发
connectedCallback,此时模板还未被添加到DOM,所有实例都会进入else分支发起请求 - 即使后续实例能查到DOM中的模板,也存在重复请求的竞争问题
正确的做法是把模板的缓存逻辑放在类的静态属性中,用Promise统一管理请求,确保无论多少实例同时初始化,只会发起一次请求。
修改后的代码如下:
// web-component.js export class WebComponent extends HTMLElement { static templateUrl = '/path/to/template.html'; // 静态属性:缓存模板的Promise,所有实例共享 static templatePromise = null; constructor() { super(); } async connectedCallback() { // 仅第一次调用时发起请求,后续复用已有Promise if (!WebComponent.templatePromise) { WebComponent.templatePromise = fetch(WebComponent.templateUrl) .then(response => response.text()) .then(html => { // 解析HTML字符串为template元素并返回 const tempContainer = document.createElement('div'); tempContainer.innerHTML = html; return tempContainer.querySelector('template'); }); } // 等待模板加载完成,所有实例共享同一结果 const template = await WebComponent.templatePromise; this.doSomething(template); } doSomething(template) { // 示例:克隆模板内容到组件(可根据需求调整) const content = template.content.cloneNode(true); console.log('复用的模板内容:', content); // 若使用shadow DOM,可执行类似以下操作: // this.attachShadow({mode: 'open'}).appendChild(content); } } document.addEventListener('DOMContentLoaded', customElements.define('web-component', WebComponent));
关键说明
- 静态属性
templatePromise:所有组件实例共享该属性,第一次初始化时创建fetch的Promise,后续实例直接复用,不会重复发起请求 - Promise特性:即使多个实例同时执行
await templatePromise,也只会触发一次fetch请求,因为Promise一旦创建就会执行,后续await仅等待同一个Promise的结果 - 模板解析:在Promise的then链中直接将HTML字符串解析为template元素,后续实例可直接使用解析后的元素,无需重复解析
你之前尝试的DOM查询方式不可靠,因为多个实例的connectedCallback会几乎同时触发,此时模板还没来得及被添加到DOM,导致所有实例都发起请求。而静态Promise的方式从根源上避免了这个竞争问题。
内容的提问来源于stack exchange,提问作者HomeSlice
相关产品推荐
相关产品推荐

