web-components构建跨平台组件

前言

最近了解到Web Components技术,允许创建可重用的定制元素(它们的功能封装在您的代码之外)并且在你的 web 应用中使用它们。

Web Components并不是一个新技术,早在2011年Alex Russel 就首次提出了 Web Components 的概念,随着时间的推移,不断有浏览器开始支持Web Components,并且市面上已经有非常成熟基于Web Components开发的UI组件库和工具,例如腾讯omi,谷歌lit

在日常开发中,会存在不同项目不同框架,vue2vue3react,如果在其中一个项目中封装组件,那么你无法在其他项目中引入它,假如你使用了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(当 元素增加、删除、修改自身属性时,被调用)等。

    js 复制代码
    class 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.createElementappend等原生方法创建所需元素。

创建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

文档:stenciljs.com/

github:github.com/ionic-team/...

​ 开发Web Components的工具集,用于在TypeScript和Web组件标准之上构建可扩展的企业级组件系统。Stencil组件可以从一个独立于框架的代码库本地分发给ReactAngularVue和传统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() :用于监听属性PropState的更新,和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使用率也在逐年递增,在跨框架复杂组件中,有着不可比拟的优势,降低了重复开发成本,有着更好的性能,原生的支持没有依赖。

参考

developer.mozilla.org/zh-CN/docs/...

stenciljs.com/

www.51cto.com/article/702...

相关推荐
程序员shen16161111 分钟前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
Ling_suu39 分钟前
SpringBoot3——Web开发
java·服务器·前端
Yvemil71 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
hanglove_lucky1 小时前
本地摄像头视频流在html中打开
前端·后端·html
维李设论1 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
2401_857600951 小时前
基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定
前端·javascript·vue.js
天之涯上上1 小时前
Pinia 是一个专为 Vue.js 3 设计的状态管理库
前端·javascript·vue.js
@大迁世界1 小时前
摆脱 `<div>`!7 种更语义化的 HTML 标签替代方案
前端·html
高山我梦口香糖2 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔2 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript