Vue 组件库开发:从零开始构建一个符合 Composition API 规范的组件库
大家好,今天我们来聊聊如何从零开始构建一个符合 Vue Composition API 规范的组件库。Composition API 作为 Vue 3 的核心特性,它提供了一种更灵活、更可维护的代码组织方式,因此,基于 Composition API 构建的组件库也更具优势。
本次讲座将涵盖以下几个方面:
- 项目初始化与环境搭建: 使用 Vite 初始化项目,配置 TypeScript 和 ESLint,为组件库开发打下坚实基础。
- 组件设计原则与规范: 探讨组件设计的通用原则,制定组件开发规范,确保组件库的一致性和可维护性。
- 基础组件开发: 以 Button 组件为例,详细讲解如何使用 Composition API 开发基础组件,包括 props 定义、事件处理、状态管理等。
- 组件文档与测试: 使用 Storybook 构建组件文档,使用 Jest 和 Vitest 进行单元测试,确保组件质量和可用性。
- 组件发布与维护: 将组件库发布到 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,包括type
和disabled
。type
prop 使用validator
验证器,确保传入的值是合法的。 - Emits: 使用
emits
选项声明组件可以触发的事件,这里声明了click
事件。 - Setup: 使用
setup
函数编写组件的逻辑,包括状态管理和事件处理。count
: 使用ref
创建一个响应式变量,用于记录点击次数。handleClick
: 定义点击事件的处理函数,当按钮被点击时,count
的值会增加,并触发click
事件。return
: 将count
和handleClick
返回,以便在 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,以便其他开发者使用。
-
修改
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 的文件,例如dist
、src
、README.md
等。
-
登录 npm:
npm login
-
发布组件库:
npm publish
发布成功后,其他开发者就可以通过 npm 安装和使用你的组件库了。
npm install my-component-library
组件库维护:
组件库发布后,我们需要定期进行维护,包括:
- 修复 Bug: 及时修复用户反馈的 Bug。
- 添加新特性: 根据用户需求添加新特性。
- 更新依赖: 定期更新组件库的依赖,以确保安全性和性能。
- 编写文档: 保持组件库的文档是最新的。
- 版本控制: 使用 Git 进行版本控制,并遵循语义化版本规范发布新版本。
总结
我们学习了如何从零开始构建一个符合 Composition API 规范的 Vue 组件库。从项目初始化、组件设计到文档编写和测试,每一步都至关重要。
持续学习与实践
组件库的开发是一个持续学习和实践的过程,希望大家能够通过本次讲座掌握基本技能,并在实践中不断提升自己的能力。后续可以深入研究组件库的架构设计、性能优化和自动化构建等方面,打造更加优秀的 Vue 组件库。