前言
最近了解到Web Components
技术,允许创建可重用的定制元素(它们的功能封装在您的代码之外)并且在你的 web 应用中使用它们。
Web Components
并不是一个新技术,早在2011年Alex Russel 就首次提出了 Web Components 的概念,随着时间的推移,不断有浏览器开始支持Web Components
,并且市面上已经有非常成熟基于Web Components
开发的UI组件库和工具,例如腾讯omi
,谷歌lit
。
在日常开发中,会存在不同项目不同框架,vue2
、vue3
、react
,如果在其中一个项目中封装组件,那么你无法在其他项目中引入它,假如你使用了Web Components
技术构建这个组件,那么你将不会受到框架的限制。
Web Components介绍
Web Components
从原生层面实现组件化,开发者开发组件时无需关注框架语言,一次开发多框架使用,使用起来也是一样的方便。
例如:
html
<!-- 使用组件 -->
<wc-button>点击</wc-button>
<!-- 挂载组件 -->
<script src="./wc-button.js"></script>
而wc-button.js
内部则是使用Web Components
提供的三个核心api来实现。
Web Components 它由三项主要技术组成
-
Custom elements(自定义元素):
通过
CustomElementRegistry
的接口实例注册自定义元素组件,获取它的实例使用window.customElements
属性。并且它提供了组件生命周期
connectedCallback
(当元素首次被插入文档DOM时调用)、disconnectedCallback
(当元素从文档 DOM 中删除时,被调用)、attributeChangedCallback
(当 元素增加、删除、修改自身属性时,被调用)等。jsclass WsButton extends HTMLElement { constructor() { super(); // ...组件内容 } } // 使用customElements.define定义一个新的自定义元素 customElements.define('ws-button', WsButton);
-
Shadow DOM(影子 DOM):用于将自定义元素与页面上其他代码相隔离,互不影响,保证不同的部分不会混在一起,可使代码更加干净、整洁。
使用api开启影子DOM,标签内部的HTML 结构会存在于#shdaow-root
,而不会在真实的dom树中出现。
js
this.attachShadow({mode: 'open'});
- HTML templates(HTML 模板)和 Slot插槽: 使用templates编写可以节省很多工作,通过包裹的内容可以直接插入到组件中,若不使用用templates,则需要使用
document.createElement
,append
等原生方法创建所需元素。
创建Web components
这里介绍基础创建组件方法
createElement创建
使用createElement
api创建节点,新建index.js
js
class WsButton extends HTMLElement {
constructor() {
super();
// 创建button
var button = document.createElement('button');
button.innerText = '点击';
// 插入标签
this.appendChild(button);
}
}
customElements.define('ws-button', WsButton);
创建index.html
html
<!-- 此处省略头 -->
<body>
<!-- 使用组件 -->
<ws-button></ws-button>
</body>
<!-- 挂载组件 -->
<script src="./index.js"></script>
<!-- 此处省略尾 -->
查看运行效果
此时是没有使用影子DOM的,如果修改了样式会导致组件和页面出现相互样式污染,尝试一下在页面添加样式影响组件样式。
html
<style>
button{
background-color: red;
}
</style>
<body>
<button>红色按钮</button>
<!-- 使用组件 -->
<ws-button></ws-button>
</body>
查看效果会发现ws-button
组件内部也受到了修改
而添加影子DOM后则不会受到影响。
js
class WsButton extends HTMLElement {
constructor() {
super();
let shadow = this.attachShadow({mode: 'open'});
// 创建button
var button = document.createElement('button');
button.innerText = '点击';
// 插入标签
shadow.appendChild(button);
}
}
customElements.define('ws-button', WsButton)
Template模版创建
在html中新建template模版
html
<body>
<template id='wc-button-template'>
<button>点击</button>
</template>
<ws-button></ws-button>
</body>
<script src="./index.js"></script>
在index中挂载组件
js
class WsButton extends HTMLElement {
constructor() {
super();
// 开启影子节点
let shadow = this.attachShadow({mode: 'open'});
// 获取模版节点
const template = document.getElementById('wc-button-template');
// 获取模版内容
let templateContent = template.content;
// 插入模版消息
shadow.appendChild(templateContent.cloneNode(true));
}
}
customElements.define('ws-button', WsButton)
效果与createElement
一致
还可以使用slot
插槽
html
<body>
<template id='wc-button-template'>
<button><slot></slot></button>
</template>
<ws-button>点击</ws-button>
</body>
<script src="./index.js"></script>
效果也是一样的
但实际开发中,使用这两种方式创建Web Components组件会比较繁琐,感觉像是回到了jquery时代,而为了更加便捷的开发,市面上已经有很多非常好的Web Components开发框架了,例如lit、stencil、skatejs 、bit等。
Stencil
Star: 11.2k
github:github.com/ionic-team/...
开发Web Components
的工具集,用于在TypeScript
和Web组件标准之上构建可扩展的企业级组件系统。Stencil
组件可以从一个独立于框架的代码库本地分发给React
、Angular
、Vue
和传统web开发人员,Stencil
生成 Web 组件,它们可以在任何主要框架或根本没有框架的情况下工作。
使用Stencil创建组件
使用npm初始化一个stencil component
csharp
npm init stencil
初始化完成后可以运行查看默认组件
sql
npm install
npm start
/src/components/my-component
则是运行的默认组件
react
import { Component, Prop, h } from '@stencil/core';
import { format } from '../../utils/utils';
@Component({
// 组件名
tag: 'my-component',
// 引入样式文件
styleUrl: 'my-component.css',
// 开启影子DOM
shadow: true,
})
export class MyComponent {
/**
* The first name
*/
@Prop() first: string;
/**
* The middle name
*/
@Prop() middle: string;
/**
* The last name
*/
@Prop() last: string;
private getText(): string {
return format(this.first, this.middle, this.last);
}
render() {
return <div>Hello, World! I'm {this.getText()}</div>;
}
}
可以看到有定义@Prop
接收参数,有定义getText
方法,通过装饰器的方式编写Web Components
组件,相比原生写法要清晰很多。
装饰器
在Stencil
中提供了非常多的装饰器,为开发提供便利。
- @Component() : 用于声明
Stencil
组件的装饰器,组件开头都需要通过@Component
进行声明。 - @Prop():用于声明公开的属性,接收参数,与react、vue中的prop类似。
- @State():用于声明组件的内部属性,外部无法更改和访问。
- @Watch() :用于监听属性
Prop
和State
的更新,和Vue的Watch相似。 - @Element() :用于声明当前组件的原生
element
引用,类似ref。 - @Method():用于声明公开的方法,组件外部也能访问。
- @Event():用于声明组件可能发出的 DOM 事件,并且提供了emit方法来触发。
- @Listen():监听子组件DOM 事件。
生命周期
Stencil
也提供了许多生命周期方法
- connectedCallback()
- disconnectedCallback()
- componentWillLoad()
- componentDidLoad()
- componentShouldUpdate(newValue, oldValue, propName): boolean
- componentWillRender()
- componentDidRender()
- componentWillUpdate()
- componentDidUpdate()
- render()
组件生成器
Stencil Cli
脚手架提供了generate
帮助我们快速创建组件模版。
css
npm generate wc-button
运行后会生成/src/components/wc-button
组件,生成完成后在index.html
中添加该组件查看效果
html
<body>
<my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
<ws-button>我是新建组件</ws-button>
</body>
运行效果
给组件添加上公共样式
在/stencil.config.ts
中添加一行公共样式目录
js
{
globalStyle: 'src/global/global.css',
}
并且新建公共样式文件global.css
,加入一些变量元素。
css
:root {
--wc-primary-color: #1890ff;
--wc-primary-color-hover: #40a9ff;
--wc-primary-color-active: #096dd9;
--wc-base-bg: #fff;
--wc-font-base-color: #333;
--wc-font-bg-color: #fff;
--wc-border-base-color: #dbdbdb;
}
修改组件文件
react
import { Component, Host, h, Prop, Event, EventEmitter } from '@stencil/core';
import classNames from 'classnames';
@Component({
tag: 'wc-button',
styleUrl: 'wc-button.css',
shadow: true,
})
export class WcButton {
// 类型参数
@Prop() type: 'primary' | 'default' = 'default';
// 点击事件
@Event() buttonClick: EventEmitter<MouseEvent>;
onClick(event: MouseEvent) {
this.buttonClick.emit(event);
}
render() {
return (
<Host>
<button onClick={e => this.onClick(e)} class={classNames('wc-button', `wc-button-${this.type}`)}>
<slot></slot>
</button>
</Host>
);
}
}
修改基础样式文件
css
:host {
display: inline-block;
}
.wc-button {
display: inline;
cursor: pointer;
padding: 4px 15px;
border-radius: 1px;
border: none;
box-shadow: none;
}
.wc-button-primary {
background-color: var(--wc-primary-color);
color: var(--wc-font-bg-color);
}
.wc-button-primary:hover {
background-color: var(--wc-primary-color-hover);
}
.wc-button-primary:active {
background-color: var(--wc-primary-color-active);
}
.wc-button-default {
background-color: var(--wc-base-bg);
color: var(--wc-font-base-color);
border: 1px solid var(--wc-border-base-color);
}
在index.html中
使用组件
html
<body>
<wc-button >基础</wc-button>
<wc-button type="primary">我是primary按钮</wc-button>
</body>
<script>
const wsButton = document.querySelector('wc-button');
wsButton.addEventListener('buttonClick', event => {
console.log('点击了',event);
})
</script>
查看最终效果
结语
当我了解到Web Components
这项技术时,我就想能否建立一个组件物料平台,使用脚手架开发和发布,统一规范,直到我看到了bit,将应用程序开发拆分为独立的组件和团队,还可以将自己开发的组件上传到云端,也可以使用云端的组件,未来有时间我会再了解一下。
总的来说,Web Components
使用率也在逐年递增,在跨框架复杂组件中,有着不可比拟的优势,降低了重复开发成本,有着更好的性能,原生的支持没有依赖。