如何在原生JS编写的Web Component中使用HTML模板
性能问题说明
你担心的重复解析问题确实存在:如果每次组件实例化都直接给innerHTML赋值模板字符串,浏览器每次都会重新解析字符串生成DOM,实例数量多的时候会有可感知的性能损耗。但这个问题很容易通过原生JS的静态缓存能力解决,不需要依赖废弃的HTML Import。
原生JS推荐方案
方案1:静态缓存Template实例(最通用,性能和原生template一致)
这个方案的核心是把模板解析逻辑只执行一次,所有组件实例复用预解析好的模板内容,性能和.html文件里的原生template完全持平:
class MyComponent extends HTMLElement { // 私有静态模板,全类共享,只会初始化一次 static #template = document.createElement('template'); // 静态块初始化模板内容,只会执行一次 static { this.#template.innerHTML = ` <style> /* 你的所有样式 */ :host { display: block; } b { color: red; } </style> <b>I'm in shadow dom!</b> <slot></slot> `; } constructor() { super(); this.attachShadow({ mode: 'open' }); // 直接克隆预解析好的模板内容,不需要每次解析字符串 this.shadowRoot.appendChild(MyComponent.#template.content.cloneNode(true)); } } customElements.define('my-component', MyComponent);
该方案优势:
- 所有内容都封装在.js文件里,不需要额外的.html资源
- 私有静态属性不会污染全局作用域,不会和其他组件的模板冲突
方案2:结合Constructable Stylesheets优化样式性能(现代浏览器推荐)
如果你的组件样式体量较大,可以用浏览器原生支持的可构造样式表进一步优化,样式只会被解析一次,所有组件实例共享同一份样式表内存:
class MyComponent extends HTMLElement { static #template = document.createElement('template'); static #sheet = new CSSStyleSheet(); static { // 初始化样式,仅解析一次 this.#sheet.replaceSync(` :host { display: block; } b { color: red; } /* 大量样式 */ `); // 模板只写HTML结构,不用内嵌style标签 this.#template.innerHTML = ` <b>I'm in shadow dom!</b> <slot></slot> `; } constructor() { super(); this.attachShadow({ mode: 'open' }); // 复用共享样式表 this.shadowRoot.adoptedStyleSheets = [MyComponent.#sheet]; this.shadowRoot.appendChild(MyComponent.#template.content.cloneNode(true)); } } customElements.define('my-component', MyComponent);
兼容性降级方案
如果需要支持不支持静态块、私有静态属性的旧环境,可以把模板初始化逻辑放到第一次实例化时执行:
class MyComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); // 第一次实例化时初始化模板,后续实例直接复用 if (!MyComponent.template) { MyComponent.template = document.createElement('template'); MyComponent.template.innerHTML = ` <style>/* 样式 */</style> <b>I'm in shadow dom!</b> <slot></slot> `; } this.shadowRoot.appendChild(MyComponent.template.content.cloneNode(true)); } } customElements.define('my-component', MyComponent);
注意事项
- 模板字符串里只放固定的静态内容,不要拼接用户输入的动态内容,避免XSS风险。动态内容请在实例化后通过
textContent、setAttribute等API单独赋值 - 可构造样式表目前在所有现代浏览器(Chrome 73+, Firefox 101+, Safari 16.4+)都已支持,无需兼容极旧浏览器的场景优先使用
内容的提问来源于stack exchange,提问作者run_the_race
相关产品推荐
相关产品推荐

