试试 Web Component: 比你想象的容易

组件化

组件化是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。近些年来前端的更新速度太快了,每当出一个新框架,我们都大喊「学不动了」,然而变化的是框架,是我们,不变的是思想, 当我们站在一个局外人的角度去审视这些框架,每个框架都有自己的策略,但殊途同归,他们都是组件化的产物,组件化的核心思想是「高内聚,低耦合」

高内聚、低耦合,是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计。 它的考量标准是类的内聚性是否高,耦合度是否低。目的是使程序模块的可重用性、移植性大大增强。

就目前我们使用的框架而言,确实给我们带来了极大的便利

组件化的好处:

  • 代码复用率提高,带来的是工作效率的提升
  • 代码可维护性、可读性提升
  • 团队效率提升

但有些问题也是不可避免的:

  • 不同技术栈的组件无法复用,比如vue的组件无法在react中使用,react的组件无法在vue中使用,而我们却要为了两套技术栈维护同样的功能
  • 对于同一个技术栈,我们的组件库也不能随便升级,因为升级可能会造成原来的功能不能使用。
  • 在我们使用组件库的时候,有时候会遇到样式冲突的问题
  • 我们提到的组件化更多的是源码层面针对不同功能的组件化,源码经过编译后并不在是组件,而是一个个的js文件

Web Components的出现,就是为了解决这些问题

Web Components

Web Components是Web原生提供的封装组件的方式,让开发者定义一些可重复使用的自定义元素。 主要包含custom elements、shadow dom、html templates部分。 下面我们通过一个例子来看看Web Components的使用

当我们要在页面上渲染如下内容, 图片

我们在页面上,只需要这样写就可以了

html 复制代码
<fruit-info
    name="apple"
    color="red"
></fruit-info>

customElements.define()

自定义元素使用前,必须先定义,所有的自定义元素都必须继承自 HTMLElement,定义的方式如下:

js 复制代码
class FruitInfo extends HTMLElement {
  constructor() {
    super();
  }
}

然后使用 customElements.define() 方法注册自定义元素,使其与自定义类关联起来

js 复制代码
customElements.define('fruit-info', FruitInfo);

自定义元素

接下来我们往自定义元素中添加一些内容,一个是name,一个是color

html 复制代码
<template id="fruitInfoTemplate">
  <div class="container">
    <div>水果名字:<span class="name"></span></div>
    <div>水果颜色:<span class="color"></span></div>
  </div>
</template>

namecolor是我们从组件外传递进来的入参,相当于react中的props,我们可以通过this.getAttribute()方法获取到这些入参,然后将这些入参渲染到组件中

js 复制代码
class FruitInfo extends HTMLElement {
       constructor() {
          super();
          let shadow = this.attachShadow( { mode: 'closed' } );

          let templateElem = document.getElementById('fruitInfoTemplate');
          let content = templateElem.content.cloneNode(true);
          content.querySelector('.name').innerText = this.getAttribute('name');
          content.querySelector('.color').innerText = this.getAttribute('color');

          shadow.appendChild(content);
       }
    }
customElements.define('fruit-info', FruitInfo);

首先我们定义了shadow,shadow是Web Components中的一个重要概念,它是一个DOM树,但是它和普通的DOM树有所不同,它是一个封闭的DOM树,外部无法访问到它的内容,我们可以通过attachShadow()方法来创建一个shadow,attachShadow()方法接收一个参数,modemode有两个值,openclosedopen表示外部可以访问到shadow中的内容,closed表示外部无法访问到shadow中的内容,我们这里使用的是closed,因为我们不希望外部访问到shadow中的内容, 然后我们通过document.getElementById()方法获取到模板,然后通过content.cloneNode()方法克隆模板,然后将克隆的模板添加到shadow中,这样我们就完成了自定义元素的定义,

组件样式

按照组件化思想,样式应该和组件在一起,应该影响外部的样式,我们把样式放在<template>

html 复制代码
<template id="fruitInfoTemplate">
  <style>
      :host {
          font-size: 20px;
      }
      .container {
          width: 300px;
          height: 200px;
          background-color: wheat;
      }

      .name {
          margin-bottom: 10px;
      }
      .color {
          background-color: #ccc;
      }
  </style>

  <div class="container">
    <div>水果名字:<span class="name"></span></div>
    <div>水果颜色:<span class="color"></span></div>
  </div>
</template>

目前为止,我们已经完成了一个简单的组件,但组件是为了复用,我们需要将组件封装起来,然后通过import的方式引入到页面中,这样我们就可以在页面中使用组件了

组件封装

我们的html还是只保留使用组件的代码

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<fruit-info
    name="apple"
    color="blue"
></fruit-info>

<script type="module">
import FruitInfo from "./FruidInfo.js";

customElements.define('fruit-info', FruitInfo);
</script>
</body>
</html>

我们把template和组件定义都抽离出去,放在一个js文件中

js 复制代码
const template = document.createElement('template');
template.innerHTML = `
<style>
      :host {
          font-size: 20px;
      }
      .container {
          width: 300px;
          height: 200px;
          background-color: wheat;
      }

      .name {
          margin-bottom: 10px;
      }
      .color {
          background-color: #ccc;
      }
  </style>

  <div class="container">
    <div>水果名字:<span class="name"></span></div>
    <div>水果颜色:<span class="color"></span></div>
  </div>
`

export default class FruitInfo extends HTMLElement {
    constructor() {
       super();
       let shadow = this.attachShadow( { mode: 'closed' } );

       let content = template.content.cloneNode(true);
       content.querySelector('.name').innerText = this.getAttribute('name');
       content.querySelector('.color').innerText = this.getAttribute('color');

       shadow.appendChild(content);
    }
}

上面有一个小的改变,我们template是动态创建出来的,组件的定义没有太大的变化,最后在使用的时候,我们通过import引入,并关联组件

通过这个例子,我们可以发现Web Components的使用非常简单,他的实现不是一个单一的技术,而是由DOM APIHTML 规范所组成。它其中就包含了:

  • custom elements
  • shadow dom
  • html templates
  • ES Modules

Web Components优势

Web Components优势都是建立在它是Web原生提供的基础上的,框架会变,但是Web原生不会变

  • 浏览器原生支持,无需引入第三方库
  • 可复用性,组件可以在不同的框架中使用,因为是原生的语法,所以我们可以在任何其他框架中使用,而不用考虑框架的束缚
  • 组件样式封装,组件样式不会影响外部样式,样式隔离带来的安全空间让我们不用再考虑样式污染问题

Web Components 的限制

  • CSS 的隔离,导致和外部 CSS 交互比较难
  • document 、 window、 document.body、 document.head是不可配置的,不能被覆盖。
  • 服务器端渲染(SSR)仍然存在问题,在服务器上渲染 Web Component 没有标准,因此每个框架的做法都略有不同
  • shadow dom的内容是不可访问的,这意味着我们在这里可能会遇到问题,比如全局弹窗,Web Componewnt中的内容获取,与window,document等交互可能并不如我们往常期望的效果一样

Web Components 的应用

目前我了解到的使用Web Components的有部分微前端框架,例如:无界、micro-app,还有一些第三方库:Lit、Omi,大家有兴趣的可以自行了解下

最后

Web Components 有着其天然的优势,能让我们的开发发生巨大变化,但目前来看,还需做出更多的努力让 Web Components 变得更加优秀,更加便利,才能让它真正的走进我们的生产环境中

相关推荐
周三有雨2 分钟前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
木古古1814 分钟前
使用chrome 访问虚拟机Apache2 的默认页面,出现了ERR_ADDRESS_UNREACHABLE这个鸟问题
前端·chrome·apache
爱米的前端小笔记24 分钟前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
loey_ln1 小时前
webpack配置和打包性能优化
前端·webpack·性能优化
建群新人小猿1 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
爱上语文1 小时前
HTML和CSS 表单、表格练习
前端·css·html
djk88881 小时前
Layui Table 行号
前端·javascript·layui
今天啥也没干1 小时前
使用 Sparkle 实现 macOS 应用自定义更新弹窗
前端·javascript·swift
痴憨道人1 小时前
openharmony sdk描述
javascript