You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让多个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

你之前的方法失败的核心原因有两点:

  1. 多个组件实例几乎同时触发connectedCallback,此时模板还未被添加到DOM,所有实例都会进入else分支发起请求
  2. 即使后续实例能查到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 03:21:02