在学习组件库开发之前,可以先回顾一下第三方组件库的使用流程,
- 安装
yarn add itcast-ui
- 全局引入
main.js
//组件对象
import ItcastUI from "itcast-ui";
//引入样式
import "itcast-ui/dist/itcast-ui.css";
//全局注册
Vue.use(ItcastUI);
3.组件使用
<hm-button>按钮</hm-button>
编写组价库
初始化项目
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, CSS Pre-processors, Linter
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported
by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i
> to invert selection)Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.?
In dedicated config files
❯ In package.json
注意开启了eslint之后会要求代码必须遵循一定规范,否则不能编译通过,下面介绍使用prettier格式化代码相关的配置
在项目的根目录下新建文件.prettierrc
配置如下:
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 140,
"semi": false
}
打开package.json
,
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"space-before-function-paren": 0
}
}
在rules增加一条规则,表示不允许在函数名后加空格
组件编写button
在component
下新建button.vue
文件,代码如下
<template>
<div class="hm-button"></div>
</template>
<script>
export default {
name: 'HmButton'
}
</script>
<style></style>
name: 'HmButton'
不是必须的,但加上它便于以后组件的注册
全局注册组件
main.js
//导入组件,HmButton为自定义名称
import HmButton from './components/button.vue'
//组件注册,HmButton.name是组件的名称
Vue.component(HmButton.name, HmButton)
插槽slot
button.vue
<button class="hm-button">
<span><slot></slot></span>
</button>
加上插槽后,就可以更改button里面的内容了,注意slot标签一般用span包裹,便于修改样式
<hm-button>1</hm-button>
<hm-button>2</hm-button>
接受传值props
button.vue
export default {
name: 'HmButton',
props: {
type: {
type: String,
default: 'default'
}
}
}
规定组件接受属性名为type的参数,并规定类型为string,如果不是这个类型会报错
示例:app.vue
向hm-button传递参数
app.vue
<hm-button type="primary">primary</hm-button>
根据type呈现不同样式
button.vue
<button :class="[`hm-button--${type}`]" class="hm-button">
<span><slot></slot></span>
</button>
这样通过传过来的type不同显示不同的样式,样式代码过长,这里只粘贴一部分,其余样式照着书写即可
button.vue
.hm-button--primary {
color: #fff;
background-color: #409eff;
border-color: #409eff;
&:hover,
&:focus {
background: #66b1ff;
border-color: #66b1ff;
color: #fff;
}
}
增加plain样式
button.vue
<button :class="[`hm-button--${type}`, { 'is-plain': plain }]" class="hm-button">
<span><slot></slot></span>
</button>
{ 'is-plain': plain }
plain如果为true,表示希望加上这个类名,同时props也需要修改
export default {
name: 'HmButton',
props: {
type: {
type: String,
default: 'default'
},
plain: {
type: Boolean,
default: false
}
}
}
增加字体图标
到码云下载仓库,将字体图标(fonts)拷贝到项目的assets目录
引入字体图标
main.js
import './assets/fonts/font.scss'
修改font.scss
[class*='hm-icon-']{
...
}
这行代码表示凡是类名以 hm-icon-
开头的都应用这段样式
图标居中
button.vue
<button :class="[`hm-button--${type}`, { 'is-plain': plain, 'is-round': round, 'is-circle': circle }]" class="hm-button">
<i v-if="icon" :class="icon"></i>
<!-- 如果没有传入任何内容,当我们没有传入任何内容时 -->
<span v-if="$slots.default"><slot></slot></span>
</button>
.hm-button [class*='hm-icon-'] + span {
margin-left: 5px;
}
该段代码在文字和图标之间增加了一定距离,v-if="$slots.default"
,$slots
可以获得所有父组件传递的插槽,增加这个判断是为了在插槽没有文字时不显示span标签
响应按钮点击事件
button.vue
<button
:class="[`hm-button--${type}`, { 'is-plain': plain, 'is-round': round, 'is-circle': circle }]"
class="hm-button"
@click="handleClick"
>
<i v-if="icon" :class="icon"></i>
<!-- 如果没有传入任何内容,当我们没有传入任何内容时 -->
<span v-if="$slots.default"><slot></slot></span>
</button>
点击后触发事件@click="handleClick"
,处理方法
methods: {
handleClick(e) {
this.$emit('click', e)
}
}
子组件并不直接处理,而是交给父组件
App.vue
<hm-button @click="fn">default</hm-button>
Dialog
dialog.vue
<template>
<div class="hm-dialog__wrapper">
<div class="hm-dialog" :style="{ width: width, top: top }">
<div class="hm-dialog__header">
<slot name="title"><span class="hm-dialog__title">提示</span></slot>
<button class="hm-dialog__headerbtn">
<i class="hm-icon-close"></i>
</button>
</div>
<div class="hm-dialog__body">
<span>这是一段信息</span>
</div>
<div class="hm-dialog__footer">
<hm-button>取消</hm-button>
<hm-button type="primary">确定</hm-button>
</div>
</div>
</div>
</template>
<slot name="title"><span class="hm-dialog__title">提示</span></slot>
指定名为title的插槽,如果使用组件时没有传递名为title插槽,则会显示span,否则将会用传递的插槽完全替换名为title的插槽。
App.vue
<hm-dialog width="55%">
<template v-slot:title>
<h3>标题</h3>
</template>
</hm-dialog>
v-slot:title
指定替换的插槽为title
默认插槽
<slot></slot>
默认插槽无需使用v-slot指定插槽名字,
<hm-dialog>默认插槽显示的内容</hm-dialog>
控制插槽显隐
dialog.vue
<div class="hm-dialog__footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
v-if="$slots.footer"
如果没有传递插槽则不显示。
App.vue
<hm-dialog
>默认插槽显示的内容
<template v-slot:footer>
<hm-button type="primary"></hm-button>
<hm-button></hm-button>
</template>
</hm-dialog>
template通过v-slot
指定插槽名字footer
,对应在需要显示插槽的位置<slot name="footer"></slot>
控制dialog的显示隐藏
App.vue
<hm-dialog :visible="visible"
>默认插槽显示的内容
<template v-slot:footer>
<hm-button type="primary" @click="visible = false">确认</hm-button>
<hm-button @click="visible = false">取消</hm-button>
</template>
</hm-dialog>
把visible变为false可以使对话框隐藏,但是不能在子组件修改visible,否则会出警告
dialog.vue
<div class="hm-dialog__wrapper" v-show="visible" @click.self="handleClose">
...
</div>
点击遮罩层触发关闭事件,@click.self
表示只有点击元素本身才会触发事件,而点击子元素不会响应事件,这样在点击对话框本身就不会误关了
dialog.vue
methods: {
handleClose() {
this.$emit('close', false)
}
}
让父组件处理关闭事件
App.vue
<hm-dialog :visible="visible" @close="close"
>
...
</hm-dialog>
close(value) {
this.visible = value
}
sync
.sync
是为了子组件修改父组件传递的值而设置的语法糖,
子组件dialog.vue
<button class="hm-dialog__headerbtn" @click="handleClose">
监听事件
handleClose() {
// this.$emit('close', false)
this.$emit('update:visible', false)
}
注意方法名必须是update:
+属性名
,父组件通过
App.vue
<hm-dialog :visible.sync="visible"
>
...
</hm-dialog>
:visible.sync
绑定属性值,这样就无需注册事件来修改visible的值了。
给dialog增加动画效果
<transition name="dialog-fade">
<div class="hm-dialog__wrapper" v-show="visible" @click.self="handleClose">
...
</div>
</transition>
.dialog-fade-enter-active {
animation: fade 0.3s;
}
.dialog-fade-leave-active {
animation: fade 0.3s reverse;
}
@keyframes fade {
0% {
opacity: 0;
transform: translateY(-20px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
Input
参数支持
参数名称 | 参数描述 | 参数类型 | 默认值 |
---|---|---|---|
placeholder | 占位符 | string | 无 |
type | 文本框类型(text/password) | string | text |
disabled | 禁用 | boolean | false |
clearable | 是否显示清空按钮 | boolean | false |
show-password | 是否显示密码切换按钮 | boolean | false |
name | name属性 | string | 无 |
事件支持
事件名称 | 事件描述 |
---|---|
blur | 失去焦点事件 |
change | 内容改变事件 |
focus | 获取的焦点事件 |
input.vue
<template>
<div class="hm-input" :class="{ 'hm-input--suffix': showSuffix }">
<input
class="hm-input__inner"
:class="{ 'is-disabled': disabled }"
:placeholder="placeholder"
:type="showPassword ? (passwordVisible ? 'text' : 'password') : type"
:name="name"
:disabled="disabled"
:value="value"
@input="handleInput"
/>
<span class="hm-input__suffix" v-if="showSuffix">
<i class="hm-input__icon hm-icon-circle-close" v-if="clearable && value" @click="clear"></i>
<i
class="hm-input__icon hm-icon-view "
:class="{ 'hm-icon-view-cative': passwordVisible }"
v-if="showPassword"
@click="passwordVisible = !passwordVisible"
></i>
</span>
</div>
</template>
placeholde, type,name,disabled,value
input.vue
<script>
export default {
name: 'HmInput',
props: {
// 提示文字
placeholder: {
type: String,
default: ''
},
// 文本类型
type: {
type: String,
default: 'text'
},
// 名字
name: {
type: String,
default: ''
},
// 禁用
disabled: {
type: Boolean,
default: false
},
value: {
type: String,
default: ''
},
// 清除按钮
clearable: {
type: Boolean,
default: false
},
// 显示密码
showPassword: {
type: Boolean,
default: false
}
},
data() {
return {
// 密码是否可见
passwordVisible: false
}
},
computed: {
// 不可清除且不是密码框则不显示图标
showSuffix() {
return this.clearable || this.showPassword
}
},
methods: {
//事件名称必须是input
handleInput(e) {
this.$emit('input', e.target.value)
},
clear() {
this.$emit('input', '')
}
}
}
</script>
App.vue
<div class="row">
<h2>输入框</h2>
<hm-input placeholder="请输入用户名" name="username" v-model="value1"></hm-input>
<h3>禁用输入框</h3>
<hm-input placeholder="请输入用户名" type="password" name="username" disabled v-model="value2"></hm-input>
<h3>可清除输入框</h3>
<hm-input placeholder="请输入用户名" name="username" clearable v-model="value3"></hm-input>
<h3>显示隐藏密码输入框</h3>
<hm-input placeholder="请输入用户名" type="password" name="username" showPassword v-model="value4"></hm-input>
</div>
封装组件库
新建项目
vue create look_ui
根目录创建两个文件夹packages
和examples
-
packages
: 用于存放所有的组件 -
examples
: 用于进行测试,把src改成examples
新增vue.config.js配置
根目录创建vue.config.js
const path = require("path");
module.exports = {
pages: {
index: {
//修改项目的入口文件,默认是src目录下的main.js,examples主要用于测试,不是必须
entry: "examples/main.js",
template: "public/index.html",
filename: "index.html"
}
},
// 扩展 webpack 配置,使 packages 加入编译
chainWebpack: config => {
//将js语法转化为浏览器支持的语法
config.module
.rule("js")
.include.add(path.resolve(__dirname, "packages"))
.end()
.use("babel")
.loader("babel-loader")
.tap(options => {
// 修改它的选项...
return options;
});
}
};
迁移组件
- 把原来项目的components中所有的组件放入到packages中
- 把原来项目的fonts目录放到packages中
入口文件
在packages
目录新建index.js
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
// 导入组件
import Button from './button'
import Dialog from './dialog'
import Input from './input'
const components = [Button, Dialog, Input]
const install = function(Vue) {
// 遍历注册全局组件
components.forEach(component => {
Vue.component(component.name, component)
})
}
// 在全局检查到Vue时自动安装插件,就不用显式调用Vue.use()
// 判断是否是直接引入文件
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
install
}
文件最后必须导出一个install方法,在examples/main.js
注册组件时,使用Vue.use()
时会调用index.js
中的install
注册所有组件
在examples中的main.js中进行导入测试
examples/main.js
import Vue from 'vue'
import App from './App.vue'
//自动寻找packages目录下的index.js,由于是默认导出,HeimaUI可以任意取
import HeimaUI from '../packages'
Vue.use(HeimaUI)
Vue.config.productionTip = false
new Vue({
render: h => h(App)
}).$mount('#app')
发布到npm
修改package.json文件
- 新增一条打包命令
package.json
"lib": "vue-cli-service build --target lib packages/index.js"
vue-cli-service build --target lib
命令用于打包,packages/index.js
是自定义的打包文件
- 执行打包命令
yarn lib
打包命令会生成dist文件夹
- 设置name、private、main、author
package.json
"name": "look_ui",
"version": "0.1.0",
"private": false,
"main": "dist/itcast-ui.umd.min.js",
"author": {
"name": "alfalfaw"
},
必须指定name
,并且private
为false
,否则无法上传到npm,"main": "dist/itcast-ui.umd.min.js"
指定import命令默认导入的文件
增加 `.npmignore文件
指定上传npm包应忽略的文件
# 忽略目录
examples/
packages/
public/
# 忽略指定文件
vue.config.js
babel.config.js
*.map
发布
- 换源
如果用nrm换过npm源,应该换回npm官方源
nrm use npm
- 登录
npm login
- 发布
npm publish
版本更新
在packages文件夹下新增组件,在examples目录下进行测试,每次上传新的版本,版本号"version": "0.1.0",
必须修改,并且不低于原来版本,否则无法更新成功。