本文参考:
https://www.cnblogs.com/max-tlp/p/9338855.html
https://www.cnblogs.com/caideyipi/p/7705052.html
本文源码下载地址
https://github.com/moyemoji/moyeSwitchButton
如果想要高效地复用先前编写的Vue
组件,简单的Ctrl C+V
肯定不是明智做法,将这些组件进行封装并发布到npm
是可选的一种方案。本文以一个开关按钮的开发过程为例,详细说明组件开发、封装及发布的全过程。
一个Vue项目一般采用webpack 的完整配置模板,但是因为我们的目标是实现一个简单Vue组件,因此可以采用简单版本的webpack配置模板。
vue init webpack-simple my-component
初始化后的项目结构如下所示:
my-component/
├── index.html
├── package.json
├── README.md
├── .babelrc
├── .editorconfig
├── .gitignore
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ └── main.js
└── webpack.config.js
我们在src文件夹下新建switchButton
作为组件源文件存放处,同时在该文件夹下新建switch-button.vue
文件以及index.js
文件。switch-button.vue
文件中为组件源码,index.js
文件实现组件注册,使得依赖本组件的工程能使用组件。修改后的项目目录长这样:
my-component/
├── index.html
├── package.json
├── README.md
├── .babelrc
├── .editorconfig
├── .gitignore
├── src
│ ├── App.vue
│ ├── switchButton
│ │ └── switch-button.vue
│ │ └── index.js
│ ├── assets
│ │ └── logo.png
│ └── main.js
└── webpack.config.js
首先实现静态的开关按钮,开始编辑switchButton
目录下的switch-button.vue
文件:
<template>
<div class='sb_wrapper'>
<span class='sb_button'></span>
</div>
</template>
<script>
export default({
name:'switch-button', // 这个name和import没关系
data(){
return {}
},
mounted(){},
methods:{}
})
</script>
<style>
.sb_wrapper{
display: inline-block;
position: relative;
width: 200px;
height: 100px;
border-radius: 50px;
border: 2px solid #ddd;
box-sizing: border-box;
background: #32CD32;
}
.sb_button{
display: inline-block;
position: absolute;
width: 92px;
height: 92px;
border-radius: 46px;
top: 2px;
right: 2px;
background: #fff;
border: 2px solid #ddd;
box-sizing: border-box;
}
</style>
那么编写出来的开关按钮怎么查看呢?我们都知道,src
目录下的App.vue
文件是Vue
项目的默认主页,因此你可以修改项目App.vue
文件,引用刚编写的开关按钮组件并显示到页面上去。 修改后的App.vue
长成这样:
<template>
<div id="app">
<switch-button></switch-button>
</div>
</template>
<script>
import switchButton from '../src/switchButton/switch-button.vue'; // 引入组件,App的template中switch-button标签名字是这里产生的,但在script中变量采用驼峰法而非短线符号
export default {
name: 'app',
data () {
return {}
},
components:{ //注册插件
switchButton
},
methods:{}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
当然,如果想要顺利看到页面内容,必须要安装相应依赖库并启动项目:
npm install
npm run dev
不出意外的话,你将得到下图一样的开关按钮:
但是静态按钮肯定是不能满足功能需求的,我们要实现一个动态的开关按钮也就是实现点击按钮以后途中小圆圈能够左右滑动效果。可以借助Vue的transiton来实现这个滑动效果,具体参见相关文档,本文直接贴出代码了:
<template>
<div class='sb_wrapper' v-bind:class='{ sb_wrapper_off: button_off }' @click='toggle()'>
<transition name="fade-on">
<span v-if='button_on' class='sb_button sb_button_on'}"></span>
</transition>
<transition name="fade-off">
<span v-if='button_off' class='sb_button sb_button_off'></span>
</transition>
</div>
</template>
<script>
export default({
name:'switch-button',
data(){
return {}
},
mounted(){},
methods:{
toggle(){
this.button_on = !this.button_on;
this.button_off = !this.button_off;
}
}
})
</script>
<style>
.sb_wrapper{
display: inline-block;
position: relative;
width: 200px;
height: 100px;
border-radius: 50px;
border: 2px solid #ddd;
box-sizing: border-box;
background: #32CD32;
}
.sb_wrapper_off{
background: #fff;
}
.sb_button{
display: inline-block;
position: absolute;
width: 92px;
height: 92px;
border-radius: 46px;
top: 2px;
background: #fff;
border: 2px solid #ddd;
box-sizing: border-box;
}
.sb_button_on{
right: 2px;
}
.sb_button_off{
left: 2px;
}
/* 开始过渡阶段,动画出去阶段 */
.fade-on-enter-active{
transition: all 0.3s ease-out;
}
/* 进入开始 */
.fade-on-enter{
transform: translateX(-100%);
}
/* 开始过渡阶段,动画出去阶段 */
.fade-off-enter-active{
transition: all 0.3s ease-out;
}
/* 进入开始 */
.fade-off-enter{
transform: translateX(100%);
}
</style>
组件封装最重要的部分,即接口部分,也就是这个组件与父组件之间的交互,包括了父组件向该封装组件的数据流入与组件向父组件输出数据。以这个开关按钮为例,我们希望在实际引用的时候,能够从父组件传入诸如按钮尺寸、初始状态等信息来定制开关按钮,也希望点击按钮时,按钮的开关状态能够向父组件反馈。
不想卖关子,简单来说,父组件向子组件传递数据可以通过组件的props
属性实现,子组件向父组件传递数据(事件触发)可以通过$emit
来实现。
组件定制具体实现原理:
子组件事件触发从而向父组件传递数据具体实现原理:
子组件代码:
<template>
<div class='sb_wrapper' v-bind:class='{ sb_wrapper_off: button_off }' :style="{'width': sb_wrapper_width + 'px', 'height': sb_wrapper_height + 'px', 'borderRadius': sb_wrapper_border_radius + 'px'}" @click='toggle()'>
<transition name="fade-on">
<span v-if='button_on' class='sb_button sb_button_on' :style="{'width': sb_button_size + 'px', 'height': sb_button_size + 'px', 'borderRadius': sb_button_border_radius + 'px'}"></span>
</transition>
<transition name="fade-off">
<span v-if='button_off' class='sb_button sb_button_off' :style="{'width': sb_button_size + 'px', 'height': sb_button_size + 'px', 'borderRadius': sb_button_border_radius + 'px'}"></span>
</transition>
</div>
</template>
<script>
export default({
name:'switch-button',
// 父元素传入的数据
props: {
wrapper_width:{
type:Number, // 类型
default:200, // 默认值
},
initial_state:{
type: Boolean,
default: false
}
},
data(){
return {
// 根据传入数据,数据绑定更改样式
sb_wrapper_width: this.wrapper_width,
sb_wrapper_height: this.wrapper_width / 2,
sb_wrapper_border_radius: this.wrapper_width / 4,
sb_button_size: this.wrapper_width / 2 - 8,
sb_button_border_radius: (this.wrapper_width / 2 - 8) / 2,
button_on: this.initial_state,
button_off: !this.initial_state
}
},
mounted(){},
methods:{
toggle(){
this.button_on = !this.button_on;
this.button_off = !this.button_off;
// 触发toggleBtn事件
this.$emit("toggleBtn", this.button_on);
}
}
})
</script>
<style>
/*
sb_button_wrapper长宽比为9:5【变量】
sb_button_wrapper的border-radius为宽的一半【变量】
sb_button_wrapper的boder宽度为2px
*/
.sb_wrapper{
display: inline-block;
position: relative;
border: 2px solid #ddd;
box-sizing: border-box;
background: #32CD32;
}
.sb_wrapper_off{
background: #fff;
}
/*
sb_button长宽为sb_button_wrapper宽度减去8px【变量】
sb_button的border-radius为长宽一半【变量】
sb_button的boder宽度为2px
sb_button的left或者right为2px【变量】
*/
.sb_button{
display: inline-block;
position: absolute;
top: 2px;
background: #fff;
border: 2px solid #ddd;
box-sizing: border-box;
}
.sb_button_on{
right: 2px;
}
.sb_button_off{
left: 2px;
}
/* 开始过渡阶段,动画出去阶段 */
.fade-on-enter-active{
transition: all 0.3s ease-out;
}
/* 进入开始 */
.fade-on-enter{
transform: translateX(-100%);
}
/* 开始过渡阶段,动画出去阶段 */
.fade-off-enter-active{
transition: all 0.3s ease-out;
}
/* 进入开始 */
.fade-off-enter{
transform: translateX(100%);
}
</style>
父组件代码:
<template>
<div id="app">
<!--父组件传入几个初始状态信息,接收事件-->
<switch-button :wrapper_width="wrapper_width" :initial_state="initial_state" v-on:toggleBtn="showState(arguments)"></switch-button>
<div>
<!-- <span>当前开关状态为:{{btnState}}</span> -->
</div>
</div>
</template>
<script>
import switchButton from '../src/switchButton/switch-button'; // 引入
/*
父组件——>switch-button:
wrapper_width:按钮的宽度
initial_state:按钮初始开启状态
switch-button——>父组件
toggleBtn:切换按钮时传回按钮状态,true表示开启,false表示关闭
*/
export default {
name: 'app',
data () {
return {
wrapper_width: 500,
initial_state: true,
btnState: ''
}
},
components:{ //注册插件
switchButton
},
methods:{
showState(val){
console.log(val);
this.btnState = val[0];
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
通过以上步骤,基本完成了组件开发了。
目的:将该组件作为 Vue
插件
此处需要注意的是 install
。 Vue
的插件必须提供一个公开方法 install
,该方法会在你使用该插件,也就是 Vue.use(yourPlugin)
时被调用。这样也就给 Vue
全局注入了你的所有的组件。
// switchButton 插件对应组件的名字
import switchButton from './switch-button';
// Vue.js 的插件应当有一个公开方法 install 。第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象
// 参考:https://cn.vuejs.org/v2/guide/plugins.html#%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
// 此处注意,组件需要添加name属性,代表注册的组件名称,也可以修改成其他的
switchButton.install = Vue => Vue.component(switchButton.name, switchButton); //注册组件
// 标签的方式引入,留到后面再另开新篇讨论
//const install = function(Vue, opts = {}) {
// Vue.component(switchButton.name, switchButton);
//}
/* 支持使用标签的方式引入 Vue是全局变量时,自动install */
//if (typeof window !== 'undefined' && window.Vue) {
// install(window.Vue);
//}
export default switchButton;
webpack.config.js
修改webpack.config.js
,修改完成后执行npm run build
看下是否生效
// 执行环境
const NODE_ENV = process.env.NODE_ENV;
console.log("-----NODE_ENV===",NODE_ENV);
module.exports = {
// 根据不同的执行环境配置不同的入口
entry: NODE_ENV == 'development' ? './src/main.js' : './src/switchButton/index.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'switchButton.js',
library: 'switchButton', // 指定的就是你使用require时的模块名
libraryTarget: 'umd', // 指定输出格式
umdNamedDefine: true // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define
},
library:指定的就是你使用require
时的模块名
libraryTarget:为了支持多种使用场景,我们需要选择合适的打包格式。常见的打包格式有 CMD、AMD、UMD,CMD
只能在 Node
环境执行,AMD
只能在浏览器端执行,UMD
同时支持两种执行环境。显而易见,我们应该选择 UMD
格式。
有时我们想开发一个库,如lodash
,underscore
这些工具库,这些库既可以用commonjs
和amd
方式使用也可以用script方式引入。
这时候我们需要借助library
和libraryTarget
,我们只需要用ES6来编写代码,编译成通用的UMD
就交给webpack
了
umdNamedDefine:会对 UMD
的构建过程中的 AMD
模块进行命名。否则就使用匿名的define
package.json
文件// 开源因此需要将这个字段改为 false
"private": false,
// 这个指 import tlp_plugin_sum 的时候它会去检索的路径
"main": "dist/switchButton.js",
.gitignore
修改git上传代码,可以将.gitignore
去掉忽略dist
, 把打包的文件也提交上去;
https://www.npmjs.com/
,注册好之后,npm login
npm publish
在你需要应用该组件的项目中,运行以下命令:
npm install my-component --save-dev
此外,需要修改main.js
import switchButton from 'my-component'
Vue.use(switchButton );
此时,在需要调用组件的页面中即可通过标签名调用啦!