Vue组件库开发:从零开始构建一个符合`composition api`规范的组件库

Vue 组件库开发:从零开始构建一个符合 Composition API 规范的组件库

大家好,今天我们来聊聊如何从零开始构建一个符合 Vue Composition API 规范的组件库。Composition API 作为 Vue 3 的核心特性,它提供了一种更灵活、更可维护的代码组织方式,因此,基于 Composition API 构建的组件库也更具优势。

本次讲座将涵盖以下几个方面:

  1. 项目初始化与环境搭建: 使用 Vite 初始化项目,配置 TypeScript 和 ESLint,为组件库开发打下坚实基础。
  2. 组件设计原则与规范: 探讨组件设计的通用原则,制定组件开发规范,确保组件库的一致性和可维护性。
  3. 基础组件开发: 以 Button 组件为例,详细讲解如何使用 Composition API 开发基础组件,包括 props 定义、事件处理、状态管理等。
  4. 组件文档与测试: 使用 Storybook 构建组件文档,使用 Jest 和 Vitest 进行单元测试,确保组件质量和可用性。
  5. 组件发布与维护: 将组件库发布到 npm,并探讨如何进行版本控制和维护。

1. 项目初始化与环境搭建

首先,我们需要使用 Vite 初始化一个 Vue 3 项目。Vite 是一个快速、轻量级的构建工具,非常适合用于组件库开发。

npm create vue@latest my-component-library

选择 TypeScript、ESLint 和 Prettier 等选项,可以帮助我们更好地管理代码质量。

接下来,安装必要的依赖:

cd my-component-library
npm install
npm install -D sass vite-plugin-dts
  • sass: 用于编写 CSS 样式,提高开发效率。
  • vite-plugin-dts: 用于生成 TypeScript 类型声明文件,方便用户使用。

修改 vite.config.ts 文件,配置组件库的构建方式:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import dts from 'vite-plugin-dts'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), dts()],
  build: {
    lib: {
      // 入口文件
      entry: resolve(__dirname, 'src/index.ts'),
      // 组件库名称
      name: 'MyComponentLibrary',
      // 打包格式
      formats: ['es', 'umd']
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue'],
      output: {
        // 为 UMD 格式提供全局变量名
        globals: {
          vue: 'Vue'
        }
      }
    }
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})

这个配置文件的作用是:

  • entry: 指定组件库的入口文件,通常是 src/index.ts
  • name: 指定组件库的名称,这个名称将用于 UMD 格式的全局变量。
  • formats: 指定组件库的打包格式,es 是 ES 模块格式,umd 是 UMD 格式。
  • external: 指定不需要打包到组件库中的外部依赖,例如 vue
  • globals: 为 UMD 格式指定全局变量名,例如 vue 的全局变量名是 Vue
  • alias: 创建 @ 别名,指向 src 目录,方便引入模块。

src 目录下创建 index.ts 文件,作为组件库的入口文件:

import MyButton from './components/MyButton.vue'

export {
  MyButton
}

2. 组件设计原则与规范

在开始开发组件之前,我们需要制定一些组件设计原则和规范,以确保组件库的一致性和可维护性。

  • 单一职责原则: 每个组件应该只负责一个明确的功能。
  • 高内聚低耦合: 组件内部的代码应该紧密相关,组件之间的依赖关系应该尽可能少。
  • 可复用性: 组件应该设计成可复用的,可以在不同的场景中使用。
  • 可配置性: 组件应该提供足够的配置选项,以满足不同的需求。
  • 易用性: 组件应该易于使用,提供清晰的 API 和文档。

基于以上原则,我们可以制定以下组件开发规范:

  • 组件命名: 使用 PascalCase 命名组件,例如 MyButton
  • Props 定义: 使用 TypeScript 定义 props,并提供默认值和类型检查。
  • 事件命名: 使用 kebab-case 命名事件,例如 my-click
  • 样式命名: 使用 BEM 规范命名样式,例如 my-button__text
  • 文档规范: 使用 Markdown 格式编写组件文档,包括组件描述、props、事件、示例等。

3. 基础组件开发

我们以 Button 组件为例,详细讲解如何使用 Composition API 开发基础组件。

首先,在 src/components 目录下创建 MyButton.vue 文件:

<template>
  <button
    class="my-button"
    :class="[type ? `my-button--${type}` : '']"
    @click="handleClick"
  >
    <slot></slot>
  </button>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'MyButton',
  props: {
    type: {
      type: String,
      default: '',
      validator: (value: string) => {
        return ['primary', 'secondary', 'success', 'warning', 'danger'].includes(value);
      }
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  emits: ['click'],
  setup(props, { emit }) {
    const count = ref(0);

    const handleClick = () => {
      if (props.disabled) return;
      count.value++;
      emit('click', count.value);
    };

    return {
      count,
      handleClick
    };
  }
});
</script>

<style lang="scss" scoped>
.my-button {
  padding: 10px 20px;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;

  &--primary {
    background-color: #007bff;
    color: #fff;
  }

  &--secondary {
    background-color: #6c757d;
    color: #fff;
  }

  &--success {
    background-color: #28a745;
    color: #fff;
  }

  &--warning {
    background-color: #ffc107;
    color: #000;
  }

  &--danger {
    background-color: #dc3545;
    color: #fff;
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}
</style>

这个组件的实现包括以下几个部分:

  • Template: 定义组件的 HTML 结构,使用 slot 插槽允许用户自定义按钮内容。
  • Props: 使用 defineComponent 定义组件的 props,包括 typedisabledtype prop 使用 validator 验证器,确保传入的值是合法的。
  • Emits: 使用 emits 选项声明组件可以触发的事件,这里声明了 click 事件。
  • Setup: 使用 setup 函数编写组件的逻辑,包括状态管理和事件处理。
    • count: 使用 ref 创建一个响应式变量,用于记录点击次数。
    • handleClick: 定义点击事件的处理函数,当按钮被点击时,count 的值会增加,并触发 click 事件。
    • return: 将 counthandleClick 返回,以便在 template 中使用。
  • Style: 使用 SCSS 编写组件的样式,使用 BEM 规范命名样式,例如 my-button__text

4. 组件文档与测试

为了确保组件的质量和可用性,我们需要为每个组件编写文档和测试。

组件文档:

我们可以使用 Storybook 构建组件文档。Storybook 是一个流行的组件开发环境,可以帮助我们快速创建和管理组件文档。

首先,安装 Storybook:

npx sb init --builder vite

然后,在 src/components/MyButton.stories.ts 文件中编写 Button 组件的 Story:

import type { Meta, StoryObj } from '@storybook/vue3';

import MyButton from './MyButton.vue';

// More on how to set up stories at: https://storybook.js.org/docs/vue/writing-stories/introduction
const meta = {
  title: 'Example/MyButton',
  component: MyButton,
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/vue/writing-docs/autodocs
  tags: ['autodocs'],
  argTypes: {
    type: {
      control: 'select',
      options: ['primary', 'secondary', 'success', 'warning', 'danger']
    },
    disabled: { control: 'boolean' },
    onClick: { action: 'clicked' },
  },
  args: { type: 'primary', disabled: false }, // default value
} satisfies Meta<typeof MyButton>;

export default meta;
type Story = StoryObj<typeof meta>;
/*
 * More on component templates: https://storybook.js.org/docs/vue/writing-stories/introduction#using-args
 */
export const Primary: Story = {
  args: {
    type: 'primary',
    children: 'Primary Button',
  },
};

export const Secondary: Story = {
  args: {
    type: 'secondary',
    children: 'Secondary Button',
  },
};

export const Success: Story = {
    args: {
      type: 'success',
      children: 'Success Button',
    },
  };

export const Warning: Story = {
    args: {
      type: 'warning',
      children: 'Warning Button',
    },
  };

  export const Danger: Story = {
    args: {
      type: 'danger',
      children: 'Danger Button',
    },
  };

export const Disabled: Story = {
  args: {
    disabled: true,
    children: 'Disabled Button',
  },
};

这个 Storybook 配置文件定义了 Button 组件的 Story,包括:

  • title: Story 的标题,用于在 Storybook 中显示。
  • component: Story 对应的组件。
  • argTypes: 组件的 props 和事件的类型定义,用于在 Storybook 中控制组件的行为。
  • args: 组件的默认 props 值。
  • Primary: 一个 Story 示例,显示一个 Primary 类型的 Button。
  • Secondary: 一个 Story 示例,显示一个 Secondary 类型的 Button。
  • Disabled: 一个 Story 示例,显示一个禁用的 Button。

运行 Storybook:

npm run storybook

可以在浏览器中访问 Storybook,查看 Button 组件的文档和示例。

单元测试:

我们可以使用 Jest 和 Vitest 进行单元测试。Jest 是一个流行的 JavaScript 测试框架,Vitest 是一个基于 Vite 的快速测试框架。

首先,安装 Jest 和 Vitest:

npm install -D jest @vue/test-utils @types/jest

然后,创建 src/components/MyButton.spec.ts 文件,编写 Button 组件的单元测试:

import { mount } from '@vue/test-utils';
import MyButton from './MyButton.vue';

describe('MyButton', () => {
  it('renders slot content', () => {
    const wrapper = mount(MyButton, {
      slots: {
        default: 'Click me'
      }
    });
    expect(wrapper.text()).toContain('Click me');
  });

  it('emits click event when clicked', async () => {
    const wrapper = mount(MyButton);
    await wrapper.find('button').trigger('click');
    expect(wrapper.emitted()).toHaveProperty('click');
  });

  it('does not emit click event when disabled', async () => {
    const wrapper = mount(MyButton, {
      props: {
        disabled: true
      }
    });
    await wrapper.find('button').trigger('click');
    expect(wrapper.emitted()).not.toHaveProperty('click');
  });
});

这个单元测试包括以下几个测试用例:

  • renders slot content: 测试组件是否正确渲染 slot 内容。
  • emits click event when clicked: 测试组件在点击时是否触发 click 事件。
  • does not emit click event when disabled: 测试组件在禁用状态下点击时是否不触发 click 事件。

修改 package.json 文件,添加测试命令:

{
  "scripts": {
    "test": "jest"
  }
}

运行单元测试:

npm run test

5. 组件发布与维护

完成组件开发和测试后,我们可以将组件库发布到 npm,以便其他开发者使用。

  1. 修改 package.json 文件:

    • name: 组件库的名称,必须是唯一的。
    • version: 组件库的版本号,遵循语义化版本规范。
    • description: 组件库的描述,用于在 npm 上显示。
    • keywords: 组件库的关键词,用于在 npm 上搜索。
    • author: 组件库的作者。
    • license: 组件库的许可证。
    • main: 组件库的入口文件,通常是 dist/my-component-library.umd.js
    • module: 组件库的 ES 模块入口文件,通常是 dist/my-component-library.es.js
    • types: 组件库的 TypeScript 类型声明文件,通常是 dist/index.d.ts
    • files: 需要发布到 npm 的文件,例如 distsrcREADME.md 等。
  2. 登录 npm:

    npm login
  3. 发布组件库:

    npm publish

发布成功后,其他开发者就可以通过 npm 安装和使用你的组件库了。

npm install my-component-library

组件库维护:

组件库发布后,我们需要定期进行维护,包括:

  • 修复 Bug: 及时修复用户反馈的 Bug。
  • 添加新特性: 根据用户需求添加新特性。
  • 更新依赖: 定期更新组件库的依赖,以确保安全性和性能。
  • 编写文档: 保持组件库的文档是最新的。
  • 版本控制: 使用 Git 进行版本控制,并遵循语义化版本规范发布新版本。

总结

我们学习了如何从零开始构建一个符合 Composition API 规范的 Vue 组件库。从项目初始化、组件设计到文档编写和测试,每一步都至关重要。

持续学习与实践

组件库的开发是一个持续学习和实践的过程,希望大家能够通过本次讲座掌握基本技能,并在实践中不断提升自己的能力。后续可以深入研究组件库的架构设计、性能优化和自动化构建等方面,打造更加优秀的 Vue 组件库。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注