# web component

### web component

web component 的目的是实现可复用的定制元素。web component与vue这类MVVM框架的组件非常相似，但是不同的地方在于，web component是原生支持的，浏览器可以识别它，不需要经过编译才能被浏览器识别。

web component有以下特点

* 原生支持，性能更好，迭代前景更好
* 无关框架，理论上任何基于js的框架都能使用
* 基于shadow DOM(影子DOM)，保证组件功能和样式的私有性，实现真正的组件化

web component由三大部分组成

1. 自定义元素。 可以在html使用自定义的标签，来实现UI。
2. 影子DOM。创建影子DOM树，并附加到主文档DOM上，并保证内部功能和样式的私有话，防止内部与外部功能和样式冲突。
3. HTML模板，使用`template` 和 `slot` ，使得元素结构能够被复用和定制。

### stenciljs

stencil是ionic团队开发的一款用于开发基于web component的系统或组件的编译器。

优点

* 大量使用装饰器，学习成本低，尤其是如果有Vue这类MVVM框架的经验时，很容易上手
* 自动生成使用文档
* 原生支持typescript和tsx
* 提供多种框架集成的案例
* 基于rollup打包，比webpack速度快
* 集成e2e测试

### 最佳实践

#### 尽量减少DOM结构

减少DOM结构不仅能够提升性能，更能使用户更方便地穿透样式。

#### 穿透样式

1. 当DOM结构比较简单使，使用inherit来继承Host上的样式
2. 提供css变量来定制关键样式
3. 尽可能地提供part，利用::part伪类元素选择器来定制样式(注意：part选择器无法选中子孙)

#### 快速开发 vs 控制反转

对于不复杂且通用的组件逻辑，提供Prop来控制，对于复杂的组件功能，提供slot来实现。换句话说就是通过Prop来实现快速开发，通过slot(控制反转思想的实现)来提供定制组件的能力。

#### 保留一个\_this指向节点

component有时候会自行调用某些方法，例如message组件会在1.5s后调用destroy方法自动销毁，而这个时候就需要访问到对应到元素节点，这里有两种解决方法，一种是为元素节点设置一个id，并且每创建一个节点就让id++，并将id保存到this中，这样就可以通过`document.querySelector('特定前缀' + this.id)`来访问真实节点；另一种方法是通过jsx ref，直接获取host到节点对象，并存储为this的一个属性上(\_this)。

第二种方法比第一种好，因为第二种不需要为host设置id，避免覆盖了用户设置的id。

更新：stencil提供@Element装饰器，可以直接获取Host元素。

### 踩坑记录

#### this的指向

stencil采用class来创建组件，而在class中this的指向有多种情况。

通常情况下this是指向实例，但是当类方法是作为事件处理程序被调用时，this则指向当前节点。

解决方法是使用箭头函数或者bind绑定this为类实例。

#### @font-face

在影子DOM使用@font-face无法正常的加载字体，无论是本地还是网络加载。目前的解决方式是通过js在主文档中创建style标签进行加载，或者直接将css引入。

### storybook集成

storybook如果接入web component会导致无法正常工作，因此在init的时候应当选择html模板。

package.json添加script命令`build:watch`，设置成`stencil build --watch`，这样就可以在编写代码的时候自动重新编译。

然后在.storybook/preview.js中添加

```javascript
import { defineCustomElements } from '../dist/esm/loader'; 
defineCustomElements();
```
