Vue封装UI库

在学习组件库开发之前,可以先回顾一下第三方组件库的使用流程,

  1. 安装
yarn add itcast-ui
  1. 全局引入
    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

根目录创建两个文件夹packagesexamples

  • 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,并且privatefalse,否则无法上传到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",必须修改,并且不低于原来版本,否则无法更新成功。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 什么是组件? 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装...
    youins阅读 9,473评论 0 13
  • 探索Vue高阶组件 高阶组件(HOC)是 React 生态系统的常用词汇,React 中代码复用的主要方式就是使用...
    君惜丶阅读 966评论 0 2
  • 探索Vue高阶组件高阶组件(HOC)是 React 生态系统的常用词汇,React 中代码复用的主要方式就是使用高...
    videring阅读 10,623评论 5 30
  • 完善中…
    胡俊_05阅读 297评论 0 0
  • 套曲(Cycle)一种由多乐章组合而成的大型器乐曲或声乐曲。如:柴可夫斯基钢琴套曲《四季》 组曲(Suite)由几...
    Caesar少校阅读 5,099评论 0 0