1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 封装 Vue.js 组件库

封装 Vue.js 组件库

时间:2023-04-06 18:21:43

相关推荐

封装 Vue.js 组件库

文章前言

笔记来源:拉勾教育 大前端高薪训练营

阅读建议:内容较多,建议通过左侧导航栏进行阅读

课程目标

开源组件库

Element-UIiView

CDD

基本介绍

CDD(Component-Driven Development) :组件驱动开发 自上而下从组件级别开始,到页面级别结束

CDD 的好处

组件在最大程度被重用并行开发可视化测试

基础回顾

处理组件的边界情况

$root

$parent / $children

$refs

依赖注入 provide / inject

方便获取最外层的成员通过依赖注入的成员,不是响应式的,应该避免修改注入(inject)进来的成员

$attrs / $listeners

$attrs 把父组件中非 prop 属性绑定到内部组件 $listeners 把父组件中的 DOM 对象的原生事件绑定到内部组件

快速原型开发

VueCLI 中提供了一个插件可以进行原型快速开发

需要先额外安装一个全局的插件@vue/cli-service-global

npm install -g @vue/cli-service-global

使用vue serve快速查看组件的运行效果

vue serve

vue serve如果不指定参数,默认会在当前目录找以下的入口

main.jsindex.jsApp.vueapp.vue

可以指定要加载的组件

# vue serve 组件路径vue serve ./src/login.vue

ElementUI

初始化 package.json

npm init -y

安装 ElementUI

vue add element

加载 ElementUI,使用 Vue.use() 安装插件

import ElemnetUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'Vue.use(ElemnetUI)

组件开发

组件分类

第三方组件基础组件业务组件

步骤条组件

样式文件steps.css

.lg-steps {position: relative;display: flex;justify-content: space-between;}.lg-steps-line {position: absolute;height: 2px;top: 50%;left: 24px;right: 24px;transform: translateY(-50%);z-index: 1;background: rgb(223, 231, 239);}.lg-step {border: 2px solid;border-radius: 50%;height: 32px;width: 32px;display: flex;justify-content: center;align-items: center;font-weight: 700;z-index: 2;background-color: white;box-sizing: border-box;}

组件文件Steps.vue

<template><div class="lg-steps"><div class="lg-steps-line"></div><div class="lg-step"v-for="index in count":key="index":style="{color: active >= index ? activeColor : defaultColor}">{{ index }}</div></div></template><script>import './steps.css'export default {name: 'LgSteps',props: {count: {type: Number,default: 3},active: {type: Number,default: 0},activeColor: {type: String,default: 'red'},defaultColor: {type: String,default: 'blue'}}}</script>

测试文件(父组件)Steps-test.vue

<template><div><steps :count="count" :active="active"></steps><hr /><button @click="next">下一步</button></div></template><script>import Steps from './Steps'export default {components: {Steps},data () {return {count: 4,active: 0}},methods: {next () {this.active < this.count && this.active++}}}</script>

使用vue serve快速查看组件的运行效果

vue serve src/Steps-test.vue

访问 http://localhost:8080,查看组件的使用效果,如图所示:

表单组件

展示功能

整体结构

展示效果

代码实现

模拟el-form组件,文件:Form.vue

<template><form><slot /></form></template><script>export default {name: 'LgForm',provide () {return {form: this}},props: {model: {type: Object},rules: {type: Object}},}</script>

模拟el-form-item组件,文件:FormItem.vue

<template><div><label>{{ label }}</label><div><slot /><p v-if="errMessage">{{ errMessage }}</p></div></div></template><script>export default {name: 'LgFormItem',inject: ['form'],props: {label: {type: String},prop: {type: String}},data () {return {errMessage: ''}},}</script>

模拟el-input组件,文件:Input.vue

<template><div><input v-bind="$attrs" :type="type" :value="value"></div></template><script>export default {name: 'LgInput',inheritAttrs: false, // 禁用继承父组件中传入的属性props: {value: {type: String},type: {type: String,default: 'text'}},}</script>

模拟el-button组件,文件:Button.vue

<template><div><button :type="type" @click="handleClick"><slot /></button></div></template><script>export default {name: 'LgButton',methods: {handleClick (evt) {this.$emit('click', evt)evt.preventDefault()}}}</script>

表单验证

ElementUI 中的表单验证,是使用了async-validator模块

安装async-validator

npm i async-validator

Input 组件验证

Input 组件中触发自定义事件 validate

Input.vue

<input v-bind="$attrs" :type="type" :value="value" @input="handleInput"><script>export default {methods: {handleInput (evt) {this.$emit('input', evt.target.value)const findParent = parent => {while (parent) {if (parent.$options.name === 'LgFormItem') {break} else {parent = parent.$parent}}return parent}const parent = findParent(this.$parent)if (parent) {parent.$emit('validate')}}}}</script>

FormItem 渲染完毕注册自定义事件 validate

FormItem.vue

<script>import AsyncValidator from 'async-validator'export default {mounted () {this.$on('validate', () => {this.validate()})},methods: {validate () {if (!this.prop) returnconst value = this.form.model[this.prop] const rules = this.form.rules[this.prop]const descriptor = {[this.prop]: rules }const validator = new AsyncValidator(descriptor)return validator.validate({[this.prop]: value }, errors => {if (errors) {this.errMessage = errors[0].message} else {this.errMessage = ''}})}}}</script>

Form 定义事件 validate

Form.vue

methods: {validate (cb) {const tasks = this.$children.filter(child => child.prop).map(child => child.validate())Promise.all(tasks).then(() => cb(true)).catch(() => cb(false))}}

Monorepo – 组件管理方式之一

两种项目的组织方式

Multirepo(Multiple Repository) 每一个包对应一个项目 Monorepo(Monolithic Repository) 一个项目仓库中管理多个模块/包

Monorepo 目录结构

packages文件夹:存放所有要开发的组件,每一个组件对应一个文件夹,每一个文件夹就是一个包,它可以单独发布到 npm 中

button文件夹:创建的组件名

__test__文件夹:存放测试的文件

dist文件夹:打包目录

src文件夹:存放源码,*.vue 文件存放的位置

index.js文件:打包的入口,使用组件,并将组件导出

import Button from './src/button.vue'// 安装插件时,进行调用Button.install = Vue => {ponent(Button.name, Button)}export default Button

LICENSE文件:存放版权信息(本次采用 MIT 协议)

package.json文件:包的描述信息,包的名称和版本

README.md文件:文档

Storybook – UI组件的开发环境

基本介绍

可视化的组件展示平台在隔离的开发环境中,以交互式的方式展示组件在主程序之外运行,独立开发组件支持的框架 React、React Native、Vue、AngularEmber、HTML、Svelte、Mithril、Riot 官网地址:/GitHub 地址:/storybookjs/storybook

基本使用

自动安装

npx -p @storybook/cli sb init --type vueyarn add vueyarn add vue-loader vue-template-compiler --dev

--type标志来指示Storybook根据该标志进行自身配置。

启动服务,并访问

npm run storybook# yarn storybook

项目构建

npm run build-storybook# yarn build-storybook

基本案例

1,在根目录创建packages文件夹,用于存放组件,一个文件夹代表一个组件,目录结构,如下图所示:

2,组件的*.vue存放在src目录下,如图所示:

3,在每一个组件中,分别创建一个stories/*.stories.js文件,用于存放 默认导出 描述组件,以及 命名出口 描述storybook

package/input/stories/input.stories.js

import LgInput from '../'// Storybook lists your stories and provides information used by addonsexport default {title: 'LgInput',component: LgInput}// 渲染组件,设置组件模板export const Text = () => ({components: {LgInput },template: '<lg-input></lg-input>',data () {return {value: 'admin'}}})export const Password = () => ({components: {LgInput },template: '<lg-input type="password" v-model="value"></lg-input>',data () {return {value: 'admin'}}})

package/form/stories/form.stories.js

import LgForm from '../'import LgFormItem from '../../formitem'import LgInput from '../../input'import LgButton from '../../button'export default {title: 'LgForm',component: LgForm}export const Login = () => ({components: {LgForm, LgFormItem, LgInput, LgButton },template: `<lg-form class="form" ref="form" :model="user" :rules="rules"><lg-form-item label="用户名" prop="username"><lg-input :value="user.username" @input="user.username=$event" placeholder="请输入用户名"></lg-input></lg-form-item><lg-form-item label="密码" prop="password"><lg-input type="password" v-model="user.password"></lg-input></lg-form-item><lg-form-item><lg-button type="primary" @click="login">登 录</lg-button></lg-form-item></lg-form>`,data() {return {user: {username: '',password: ''},rules: {username: [{required: true,message: '请输入用户名'}],password: [{required: true,message: '请输入密码'},{min: 6,max: 12,message: '请输入6-12位密码'}]}}},methods: {login() {console.log('button')this.$refs.form.validate(valid => {if (valid) {alert('验证成功')} else {alert('验证失败')return false}})}}})

注意:

​ 此处引入了formitem组件,由于formitem组件依赖于async-validator包,因此,需要在formitem组件中安装async-validator包,否则会报错

4,修改storybook的配置文件./storybook/main.js

module.exports = {"stories": ["../packages/**/*.stories.js"],"addons": ["@storybook/addon-links","@storybook/addon-essentials"]}

5,启动服务,测试组件

npm run storybook# yarn storybook

6,访问网址,如图所示:

yarn workspace

项目依赖

基本介绍

Yarn Workspaces允许用户在单个根package.json文件的子文件夹中从多个package.json文件中安装依赖。

通过防止Workspaces中依赖包的重复,使原生WorkspacesYarn可以实现更快更轻松的依赖安装。Yarn还可以在依赖于彼此的Workspaces之间创建软链接,并确保所有目录的一致性和正确性。

开启 yarn 的工作区

项目根目录的package.json

{"private": true,"workspaces": ["packages/*"],}

"private": true:将项目提交到 GitHub 或 NPM 的时候,禁止把当前根目录的内容进行提交

"workspaces": []:存放所有要管理的包路径

基本使用

给工作区根目录安装开发依赖

yarn add jest-D -W

jest:FaceBook 发布的单元测试工具-D:开发依赖-W:工作区,指的是安装到工作区的根目录

给指定工作区安装依赖

yarn workspace lg-button add lodash@4

lg-button:包名,即在package.json中设置的name属性

给所有的工作区安装依赖

yarn install

总结

重复的依赖包会提升到根目录下的node_modules目录中,单独的依赖会安装到对应的组件的node_modules目录中,方便管理依赖。

Lerna

基本介绍

Lerna 是一个优化使用 git 和 npm 管理多包仓库的工作流工具用于管理具有多个包的 JavaScript 项目它可以一键把代码提交到git和npm仓库

基本使用

全局安装

yarn global add lerna

初始化

lerna init

发布

lerna publish

清理项目中的node_modules

lerna clean

注意:

在发布之前,需要建立远端 Git 仓库,并且连接本地项目

在发布之前,还需要 注册 / 登录 npm

# 注册npm adduser# 登录npm login

登录结果,如图所示:

查看登录账户,如下所示:

npm whoami

查看当前的镜像源

npm config get registry

查看是否是npm官网,如果不是的话,需要将镜像源修改回来

Vue 组件的单元测试

组件单元测试的好处

提供描述组件行为的文档节省手动测试的时间减少研发新特性时产生的bug改进设计促进重构

安装依赖

Vue Test Utils:Vue提供的组件单元测试官方库,需要结合单元测试框架一起使用

Jest:FaceBook 开发的单元测试框架

vue-jest:预处理器

babel-jest:对测试代码进行降级处理,即将 ES6 语法转换为 ES5等

安装

yarn add jest @vue/test-utils vue-jest babel-jest -D -W

配置测试脚本

package.json

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

Jest 配置文件

jest.config.js

module.exports = {// 用哪里找测试文件"testMatch": ["**/__tests__/**/*.[jt]s?(x)"],// 测试文件中导入的模块后缀"moduleFileExtensions": ["js","json",// 告诉 Jest 处理 `*.vue` 文件"vue"],"transform": {// 用 `vue-jest` 处理 `*.vue` 文件".*\\.(vue)$": "vue-jest",// 用 `babel-jest` 处理 js".*\\.(js)$": "babel-jest"}}

Babel 配置文件

babel.config.js

module.exports = {presets: [['@babel/preset-env']]}

Babel 桥接

安装 Babel 的桥接依赖

yarn add babel-core@bridge -D -W

Jest 常用 API

全局函数 describe(name, fn) 把相关测试组合在一起test(name, fn) 测试方法expect(value) 断言 匹配器 toBe(value) 判断值是否相等toEqual(obj) 判断对象是否相等toContain(value) 判断数组或者字符串中是否包含… 快照 toMatchSnapshot()

Vue Test Utils 常用 API

mount() 创建一个包含被挂载和渲染的 Vue 组件的 Wrapper Wrapper vm wrapper 包裹的组件实例props() 返回 Vue 实例选项中的 props 对象html() 组件生成的 HTML 标签find() 通过选择器返回匹配到的组件中的 DOM 元素trigger() 触发 DOM 原生事件,自定义事件 wrapper.vm.$emit()…

编写测试文件

__tests__/input.test.js

import input from '../src/input.vue'import {mount } from '@vue/test-utils'describe('lg-input', () => {test('input-text', () => {const wrapper = mount(input)expect(wrapper.html()).toContain('input type="text"')})test('input-password', () => {const wrapper = mount(input, {propsData: {type: "password"}})expect(wrapper.html()).toContain('input type="password"')})test('input-password', () => {const wrapper = mount(input, {propsData: {type: "password",value: 'admin'}})expect(wrapper.props('value')).toBe('admin')})test('input-snapshot', () => {const wrapper = mount(input, {propsData: {type: "password",value: 'admin'}})expect(wrapper.vm.$el).toMatchSnapshot()})})

执行yarn test,测试结果,如图所示:

生成的快照文件,如图所示:

Rollup

基本介绍

Rollup 是一个模块打包器Rollup 支持 Tree-shaking打包的结果比 Webpack 要小开发框架/组件库的时候使用 Rollup 更合适

安装依赖

Rollup

rollup-plugin-terser:对代码进行压缩

rollup-plugin-vue@5.1.9:把单文件组件编译成 js 代码,注意:一定要指定版本

vue-template-compiler:编译器

安装

yarn add rollup rollup-plugin-terser rollup-plugin-vue@5.1.9 vue-template-compiler -D -W

Rollup 配置文件

在 button 目录中创建rollup.config.js

import {terser } from 'rollup-plugin-terser'import vue from 'rollup-plugin-vue'module.exports = [{input: 'index.js',output: [{file: 'dist/index.js',format: 'es'}],plugins: [vue({// Dynamically inject css as a <style> tagcss: true, // Explicitly convert template to render functioncompileTemplate: true}),terser()]}]

打包命令

打包单个组件

组件目录的package.json中配置scripts

"scripts": {"build": "rollup -c"}

进入button目录下,执行打包命令

yarn workspace alison-button run build

打包所有组件

安装依赖

yarn add @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve -D -W

项目根目录创建rollup.config.js

import fs from 'fs'import path from 'path'import json from '@rollup/plugin-json'import vue from 'rollup-plugin-vue'import postcss from 'rollup-plugin-postcss'import {terser } from 'rollup-plugin-terser'import {nodeResolve } from '@rollup/plugin-node-resolve'const isDev = process.env.NODE_ENV !== 'production'// 公共插件配置const plugins = [vue({// Dynamically inject css as a <style> tagcss: true,// Explicitly convert template to render functioncompileTemplate: true}),json(),nodeResolve(),postcss({// 把 css 插入到 style 中// inject: true,// 把 css 放到和js同一目录extract: true})]// 如果不是开发环境,开启压缩isDev || plugins.push(terser())// packages 文件夹路径const root = path.resolve(__dirname, 'packages')module.exports = fs.readdirSync(root)// 过滤,只保留文件夹.filter(item => fs.statSync(path.resolve(root, item)).isDirectory())// 为每一个文件夹创建对应的配置.map(item => {const pkg = require(path.resolve(root, item, 'package.json'))return {input: path.resolve(root, item, 'index.js'),output: [{exports: 'auto',file: path.resolve(root, item, pkg.main),format: 'cjs'},{exports: 'auto',file: path.join(root, item, pkg.module),format: 'es'},],plugins: plugins}})

在每一个包中设置package.json中的mainmodule字段

{"main": "dist/cjs/index.js","module": "dist/es/index.js"}

根目录的package.json中配置scripts

"scripts": {"build": "rollup -c"}

根目录下,启动服务

yarn build

设置环境变量

安装cross-env

yarn add cross-env -D -W

根目录的package.json中配置scripts

"scripts": {"build:prod": "cross-env NODE_ENV=production rollup -c","build:dev": "cross-env NODE_ENV=development rollup -c"},

启动构建

# 生产环境打包yarn build:prod# 开发环境打包yarn build:dev

清理指定文件

安装rimraf

yarn add rimraf -D -W

在每个组件目录的package.json中配置scripts

"scripts": {"del": "rimraf dist"},

dist:是指要被删除的目录

批量执行每个组件的del命令

yarn workspaces run del

基于模板生成组件结构

使用 plop 创建模板,具体用法,参考网址:/zimeng303/article/details/110037847

安装plop

yarn add plop -D -W

模板目录结构,如图所示:

__tests/component.test.hbs

import {mount } from '@vue/test-utils'import Element from '../src/{{name}}.vue'describe('Lg-{{properCase name}}', () => {})

src/component.hbs

<template><div></div></template><script>export default {name: 'Lg{{properCase name}}',props: {}}</script><style></style>

stories/component.stories.hbs

import Lg{{properCase name}} from '../src/{{name}}.vue'export default {title: 'Lg{{properCase name}}',component: Lg{{properCase name}}}export const {{properCase name}} = _ => ({components: {Lg{{properCase name}} },template: `<div><lg-{{name}}></lg-{{name}}></div>`})

index.hbs

import Lg{{properCase name}} from './src/{{name}}.vue'Lg{{properCase name}}.install = Vue => {ponent(Lg{{properCase name}}.name, Lg{{properCase name}})}export default Lg{{properCase name}}

LICENSE,固定写法

The MIT License (MIT)Copyright (c) -presentPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included inall copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE.

package.hbs

{"name": "lg-{{name}}","version": "0.0.0","description": "lg-{{name}} component","main": "dist/cjs/index.js","module": "dist/es/index.js","license": "MIT","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"dependencies": {},"keywords": []}

在根目录配置 plop 的配置文件plopfile.js

module.exports = plop => {plop.setGenerator('component', {description: 'create a custom component',prompts: [{type: 'input',name: 'name',message: 'component name',default: 'MyComponent'}],actions: [{type: 'add',path: 'packages/{{name}}/src/{{name}}.vue',templateFile: 'plop-template/component/src/component.hbs'},{type: 'add',path: 'packages/{{name}}/__tests__/{{name}}.test.js',templateFile: 'plop-template/component/__tests__/component.test.hbs'},{type: 'add',path: 'packages/{{name}}/stories/{{name}}.stories.js',templateFile: 'plop-template/component/stories/component.stories.hbs'},{type: 'add',path: 'packages/{{name}}/index.js',templateFile: 'plop-template/component/index.hbs'},{type: 'add',path: 'packages/{{name}}/LICENSE',templateFile: 'plop-template/component/LICENSE'},{type: 'add',path: 'packages/{{name}}/package.json',templateFile: 'plop-template/component/package.hbs'},{type: 'add',path: 'packages/{{name}}/README.md',templateFile: 'plop-template/component/README.hbs'}]})}

在根目录的package.json中配置scripts

"scripts": {"plop": "plop"},

启动 plop,生成组件

yarn plop

执行命令过程中,输入名称,如图所示:

生成的组件目录结构,如图所示:

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。