组件化
组件化是前端的发展方向,现在流行的 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>
name
和color
是我们从组件外传递进来的入参,相当于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()
方法接收一个参数,mode
,mode
有两个值,open
和closed
,open
表示外部可以访问到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 API
和 HTML
规范所组成。它其中就包含了:
- 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
变得更加优秀,更加便利,才能让它真正的走进我们的生产环境中