实践 - Web Components + Vue + TypeScript 开发组件库

前言

前端开发中,我们经常需要使用一些组件库来提高开发效率、提升用户体验。然而,使用现成的组件库有时会带来新的问题,如组件的样式和功能不符合需求,代码冗余严重等。因此,我们有时需要自己开发一套组件库来满足项目的具体需求。而本文将介绍一种开发自己的组件库的方法,即使用 Web Components + Vue + TypeScript 来开发。

Web Components

Web Components 是一种浏览器标准化的技术,是由自定义元素、Shadow DOM 和 HTML Template 三个规范组成的。自定义元素可以让我们自己定义 HTML 标签,Shadow DOM 可以让我们将 DOM 提供给外部,但在内部进行封装管理,HTML Template 则可以将 DOM 结构提前预定义好,使得使用方只需要将数据传入即可。

通过 Web Components,我们可以封装我们的组件,并提供一个正式的API来使用。具体来说,我们可以通过以下步骤开发一个 Web Component:

  1. 使用 customElements.define() 方法创建一个自定义元素。
  2. 在自定义元素的类里面使用 Shadow DOM 来封装我们的组件。
  3. 将组件的样式和行为封装在自定义元素的类里面。
  4. 在 HTML 中使用我们的组件。

如果要想更深入了解 Web Components 的细节,可以参考 MDN 文档

Vue

Vue 是一个渐进式框架,它可以帮助我们构建复杂的应用程序界面。Vue 在 React 和 Angular 的基础上发展而来,它具有易于学习、灵活和高性能等特点。通过使用 Vue,我们可以方便地开发复杂的组件,Vue 组件也可以与 Web Components 自然集成。

TypeScript

TypeScript 是 JavaScript 的一个超集,它可以给 JavaScript 添加静态类型检查和一些其他的语言特性,如类、模块等。通过 TypeScript 能够使我们编码更加规范、代码更加清晰。Vue 和 Web Components 都可以使用 TypeScript 进行开发。

实践

在了解了 Web Components、Vue 和 TypeScript 之后,我们接下来将介绍如何使用它们来开发自己的组件库。

创建一个简单的 Web Component

在开始 Vue 和 TypeScript 的开发之前,我们先来熟悉一下 Web Components 的开发。我们将创建一个简单的 hello-world 组件来演示。

创建一个 hello-world.ts 文件,其中代码如下:

class HelloWorld extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    const p = document.createElement('p');
    p.innerText = 'Hello World';
    shadow.appendChild(p);
  }
}

customElements.define('hello-world', HelloWorld);

在这个文件中,我们创建了一个 HelloWorld 类,继承自 HTMLElement。我们通过 customElements.define() 方法将这个自定义元素注册到浏览器中,并起名为 hello-world

HelloWorld 的构造函数中,我们首先调用 super() 来调用父类的构造函数,然后将组件内部的元素封装到 Shadow DOM 中,并将 Hello World 文字展示在一个 p 元素中。

接下来,我们在 HTML 文件中引入这个 hello-world.js 文件,并在需要使用的位置添加 hello-world 自定义元素。例如:

<!DOCTYPE html>
<html>
<head>
  <title>Hello World Example</title>
  <script src="hello-world.js"></script>
</head>
<body>
  <hello-world></hello-world>
</body>
</html>

我们可以看到,这样我们就创建了一个简单的 Web Components,可以在 HTML 中使用。

在 Vue 中使用 Web Component

接下来让我们来介绍如何在 Vue 中使用 Web Component。

我们将使用 Vue CLI 创建一个新的项目。在创建项目时,可以选择使用 TypeScript 来进行开发。

src/components 目录下创建一个 HelloWorld.vue 文件,其中代码如下:

<template>
  <hello-world />
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import HelloWorld from '@/components/hello-world';

@Component
export default class HelloWorldVue extends Vue {
  mounted() {
    customElements.define('hello-world', HelloWorld);
  }
}
</script>

在这个文件中,我们使用了 Vue 的 @Component 注解来定义了一个 HelloWorldVue 类,并在其中通过 @vue-property-decoratormounted 钩子方法来定义了一个自定义元素。在 mounted 中,我们通过 customElements.define() 方法注册了一个名为 hello-world 的自定义元素,并将其关联到了具体的实现类 HelloWorld 上。

此外,我们在这个文件中的模板中也使用这个 hello-world 自定义元素,这样,我们就将 hello-world 结合到了 Vue 的组件中,可以在 Vue 项目中使用了。

开始开发组件库

有了以上的铺垫,我们就可以开始开发组件库了。我们的组件库将包含一个 button 组件和一个 modal 组件。

开发 Button 组件

我们在 src/components 目录下创建一个 Button.vue 文件,其中代码如下:

<template>
  <button :class="classes" :disabled="disabled" @click="clickHandler"><slot /></button>
</template>

<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';

@Component
export default class Button extends Vue {
  @Prop(String) readonly type!: string;
  @Prop({ default: false }) readonly disabled!: boolean;

  get classes() {
    return `btn ${this.type || ''}`;
  }

  clickHandler() {
    this.$emit('click');
  }
}
</script>

<style>
.btn {
  border: none;
  border-radius: 4px;
  padding: 8px 16px;
  font-size: 14px;
  cursor: pointer;
}

.btn-primary {
  background-color: #2077f5;
  color: #fff;
}

.btn-primary:hover {
  background-color: #1859c0;
}

.btn-danger {
  background-color: #f5222d;
  color: #fff;
}

.btn-danger:hover {
  background-color: #cf1322;
}
</style>

在这个文件中,我们定义了一个 Button 类,并使用 @vue-property-decorator 来修饰为 Vue 组件。我们使用 @Prop 来定义了 typedisabled 两个属性,并使用 get classes() 方法返回样式类。在模板中,我们将 disabled 属性和 click 事件直接绑定到了 button 标签上,并在其中使用了 slot 插槽将按钮的文字内容提供出去。

此外,在样式中,我们也定义了两种不同类型的按钮,分别为 btn-primarybtn-danger,并为其设置了不同的样式。

开发 Modal 组件

我们在 src/components 目录下创建一个 Modal.vue 文件,其中代码如下:

<template>
  <div class="modal" :class="{ 'is-active': visible }">
    <div class="modal-background" @click="closeHandler" />
    <div class="modal-content">
      <slot />
    </div>
    <button class="modal-close" aria-label="close" @click="closeHandler" />
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';

@Component
export default class Modal extends Vue {
  @Prop({ default: false }) readonly visible!: boolean;

  @Watch('visible', { immediate: true })
  visibleChanged(newValue: boolean) {
    if (newValue) {
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = '';
    }
  }

  closeHandler() {
    this.$emit('update:visible', false);
  }
}
</script>

<style>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9999;
  display: none;
}

.modal.is-active {
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-background {
  background-color: rgba(0, 0, 0, 0.5);
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}

.modal-content {
  background-color: #fff;
  border-radius: 4px;
  padding: 24px;
  max-height: 90%;
  overflow: auto;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  width: 80%;
}

.modal-close {
  background-color: transparent;
  border: none;
  position: absolute;
  top: 0;
  right: 0;
  width: 40px;
  height: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
  color: #999;
  cursor: pointer;
}
</style>

在这个文件中,我们定义了一个 Modal 类,并使用 @vue-property-decorator 来修饰为 Vue 组件。我们使用 @Prop 来定义了 visible 属性,并使用 watch 监听 visible 的变化来控制 body 滚动条的显示。在模板中,我们根据 visible 属性的值来动态控制 .modal 样式类的显示。我们还定义了一个 closeHandler 方法来控制 Modal 的关闭。

此外,在样式中,我们设置了 Modal 的样式,并将 .modal-close 样式定义为了不可见的状态。

使用组件

我们在 src/components 目录下创建一个 index.ts 文件,其中代码如下:

import Vue from 'vue';
import HelloWorldVue from './hello-world.vue';
import Button from './button.vue';
import Modal from './modal.vue';

const Components = {
  HelloWorldVue,
  Button,
  Modal,
};

Object.keys(Components).forEach((name) => {
  Vue.component(name, Components[name]);
});

export default Components;

在这个文件中,我们导出了三个组件,分别为 HelloWorldVueButtonModal,并将这些组件注册到了 Vue 组件中。

最后,在我们的 Vue 项目中使用这些组件非常简单,例如在 App.vue 中使用 Button 组件:

<template>
  <div id="app">
    <h1>My Vue Component Library</h1>
    <Button type="btn-primary" @click="showModal" :disabled="disabled">Show Modal</Button>
    <Modal v-model="showingModal" />
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { Button, Modal } from '@/components';

@Component({
  components: {
    Button,
    Modal,
  },
})
export default class App extends Vue {
  disabled = false;
  showingModal = false;

  showModal() {
    this.showingModal = true;
  }
}
</script>

<style>
#app {
  text-align: center;
}

h1 {
  font-size: 30px;
  margin-top: 60px;
}
</style>

在这个文件中,我们首先导入了 ButtonModal 组件,并在 @Componentcomponents 属性中注册这些组件。然后在模板中使用 Button 组件来触发 showModal 方法,同时传递了 typedisabled 两个属性。最后,我们又在模板中使用了 Modal 组件,并通过 v-model 来绑定 showingModal 属性的值,从而控制 Modal 组件的显示和隐藏。

总结

Web Components、Vue 和 TypeScript 是相互独立的技术,但是它们可以相互结合,让我们更加高效地开发 Web 应用。通过本文中的实践,我们了解了如何使用 Web Components 、Vue 和 TypeScript 来开发组件库,以及如何将这些组件集成到 Vue 项目中。最终,我们实现了一个包含 ButtonModal 两个组件的组件库。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65acbd0fadd4f0e0ff651b93