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...

相关推荐
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼3 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風7 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#