繁体   English   中英

以关注点分离组织多个 web 组件

[英]Organizing multiple web components with seperation of concerns

我正在构建一个小型应用程序,并计划使用 web 个组件(而不是使用 UI 库)。 我不打算使用任何捆绑器等,因为这将是一个小型个人网站。

我想将每个 web 组件存储在一个单独的 ES6 JS 模块文件中,这是我的设置示例:

你好-pl.net.mjs

export class HelloPlanet extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback(){
    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    let planet = this.getAttribute('planet')
    shadowRoot.innerHTML = `<p>hello ${planet}</p>`;
  }
}

你好-world.mjs

export class HelloWorld extends HTMLElement {
  constructor(){
    super()
  }
  connectedCallback(){
    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    ['Mercury','Venus','Earth','Mars','Jupiter','Saturn','Uranus','Neptune'].forEach(planet=>{
      let el = document.createElement('hello-planet')
      el.setAttribute('planet', planet);
      shadowRoot.appendChild(el)
    })
  }
}

主程序

// ordering of imports does not matter
import {HelloPlanet} from './hello-planet.mjs';
import {HelloWorld} from './hello-world.mjs';

customElements.define('hello-world', HelloWorld);
customElements.define('hello-planet', HelloPlanet);

// this will typically be handled by a client side router (e.g. Navigo)
let el = document.createElement('hello-world');
let body = document.querySelector('body');
body.appendChild(el);

index.html (只调用main.mjs,浏览器会下载脚本的rest)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="main.mjs" type="module"></script>
  <title>Web components test</title>
</head>
<body>
</body>
</html>

问题:

  1. 到目前为止,我在遇到的任何示例中都没有看到这种方法,所以想知道这是否是一种好的方法? 或者在组织 web 个组件方面有比这更好的方法。
  2. 当我需要模板+样式时,如何扩展这种方法以读取来自不同文件的模板,即 html 和 css 在单独的文件中(因此我们有关注点分离)? 我看过这个但不确定如何适应我的设置。 我也经历过这个- 但它似乎已经太复杂了,甚至不确定它是否可以处理复杂的脚本。

谢谢!

  1. 到目前为止,我在遇到的任何示例中都没有看到这种方法,所以想知道这是否是一种好的方法? 或者在组织 web 个组件方面有比这更好的方法。

完全没问题。 以编程方式创建元素有很多优点,主要是不需要查询自己的影子根来访问子元素/组件。 如果需要,您可以直接持有引用,甚至可以在 class 属性中创建引用,例如:

export class HelloWorld extends HTMLElement {
  planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
    .map(
      planet => Object.assign(document.createElement('hello-planet'), { planet })
    )
  )
  
  constructor() {
    super().attachShadow({ mode: 'open' }).append(...this.planets);
  }
}

旁注:创建影子根可以而且应该安全地在构造函数中完成。

  1. 当我需要模板+样式时,如何扩展这种方法以读取来自不同文件的模板,即 html 和 css 在单独的文件中(因此我们有关注点分离)?

对于 CSS,我们有CSS 模块脚本

import styles from './hello-world.css' assert { type: 'css' }

然后,在你的构造函数中,做

constructor() {
  // ...
  this.shadowRoot.adoptedStylesheets.push(styles);
}

对于 HTML,不幸的是,此导入功能仍在进行中

他们在 React 中的做法是将所有组件导入一个文件,然后将该组件插入到 HTML 页面中 ID 为 root 的静态 div 中。

所以你的索引文件:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="main.mjs" type="module"></script>
  <title>Web components test</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

比起你想在 DOM 中显示的任何内容,你都可以通过 JS 插入到该 div 中。

我相信这可能有助于解决您的问题,老实说,您的问题并不是 100% 清楚

使用import assert加载组件的 HTML 和 CSS 可能是前进的通用方法,但是,浏览器支持仍然有限。 要创建具有关注点分离和有限构建工具(如捆绑器)的 web 组件,您可以使用标准fetch顶级等待,以确保组件在使用前适当地引导。

在将现代 web 组件开发为ES 模块时,我同样希望分离关注点并最大限度地减少工具。 我已经开始编写有关web 组件最佳实践的文档 (WIP),我希望在开发tts-element时遵循这些最佳实践。

这是在import期间引导组件的相关设置工作:

const setup = async () => {
  const url = new URL(import.meta.url)
  const directory = url.pathname.substring(0, url.pathname.lastIndexOf('/'))
  const baseUrl = `${url.origin}${directory}`
  const [html, css] = await Promise.all([
    fetch(`${baseUrl}/template.html`).then((resp) => resp.text()),
    fetch(`${baseUrl}/styles.css`).then((resp) => resp.text())
  ])
  const parser = new DOMParser()
  const template = parser
    .parseFromString(html, 'text/html')
    .querySelector('template') as HTMLTemplateElement
  const style = document.createElement('style')

  style.textContent = css
  template.content.prepend(style)

  return class TextToSpeech extends HTMLElement {/* implementation */}
}

以下是组件将如何导出以允许使用顶级等待:

export default await setup()

这是一个示例,说明如何以各种方式(全部使用顶级等待)加载tts-element以控制define组件的时间。

  • 加载defined.js将自动以默认名称text-to-speech注册该组件。
  • 在加载defined.js?name=custom-name时使用name查询参数将使用提供的自定义名称注册组件。
  • 加载element.js需要手动注册组件定义,即消费者将负责调用define()

检查开发控制台中的 .network 选项卡以查看 HTML/CSS/JS 是如何加载的:

 <:DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8" /> <title>tts-element combined example</title> <style> text-to-speech:not(,defined): my-tts:not(,defined): speech-synth:not(:defined) { display; none: } </style> <script type="module" src="https.//unpkg.com/tts-element/dist/text-to-speech/defined:js"></script> <script type="module" src="https.//cdn.jsdelivr.net/npm/tts-element@0.0.3-beta/dist/text-to-speech/defined?js:name=my-tts"></script> <script type="module"> import ctor from 'https.//unpkg.com/tts-element/dist/text-to-speech/element.js' customElements,define('speech-synth'. ctor) </script> </head> <body> <text-to-speech>Your run-of-the-mill text-to-speech example.</text-to-speech> <my-tts>Example using the "name" query parameter.</my-tts> <speech-synth>Example using element.js.</speech-synth> </body> </html>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM