前言
现在前端开发中使用 Vue 的估计比较多,组件也是 Vue 比较核心的内容,也是我们平常开发中接触最多的东西了,那么什么是组件呢?组件可以是小到一个按钮,一个图标,也可以大到一个页面,甚至是一个系统。
最近接手了一个 Vue 的项目,可以说是学到了不少东西,当然也包括本篇说的“组件”,所以想着写下来分享一下。这篇文章不讲组件的基础,只讲创建组件(注册)的几种方式。
全局注册
Vue.component("my-component-name", {
// ... 选项 ...
});
我们可以用这种方式来创建全局组件,在实例化 Vue 之前用 Vue.component
来创建组件,这样我们可以在任何实例化 Vue 的组件(new Vue
)中使用。
具体方式
1、注册组件
Vue.component("component-a", {
/* ... */
});
Vue.component("component-b", {
/* ... */
});
Vue.component("component-c", {
/* ... */
});
new Vue({ el: "#app" });
2、使用组件
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
这样我们就成功创建注册并使用了组件,并且我们还可以在所创建的组件内部相互使用。
局部注册
我们可以以普通对象的方式创建组件,对象中可以包含我们常用的 data
、methods
等属性和方法,比如下面这样
var ComponentA = {
/* ... */
};
var ComponentB = {
/* ... */
};
var ComponentC = {
/* ... */
};
注册局部组件的话。需要我们在要使用组件的地方(大多数情况下也是组件)使用 components
来注册,具体代码如下
new Vue({
el: "#app",
components: {
"component-a": ComponentA,
"component-b": ComponentB,
},
});
单文件组件
可能更多时候我们是使用 webpack
来构建我们的项目(比如:vue-cli
),写的更多的是单文件组件,需要在组件中使用另一个组件,下面我就写一下
我们就在 vue-cli3
的基础上写,首先我们像上面那种方式一样创建一个组件
<template>
<transition name="fade" v-if="isShow">
<div class="dialog-page">
<div class="dialog-box">
<h3 class="title">{{ title }}</h3>
<span class="close">X</span>
<el-button type="primary" class="confirm-btn">确定</el-button>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "Dialog",
props: {
title: {
type: String
}
},
data() {
return {
isShow: false
}
},
mounted() {
this.isShow = true;
}
}
</script>
<style lang="scss" scoped>
.dialog-page {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.6);
z-index: 99999;
.dialog-box {
position: absolute;
top: 20%;
left: 50%;
transform: translateX(-50%);
margin: auto;
width: 700px;
height: 400px;
padding-top: 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 6px 0 #fff;
.title {
text-align: center;
font-size: 22px;
color: #333;
}
.close {
position: absolute;
right: 20px;
top: 20px;
cursor: pointer;
}
.confirm-btn {
width: 80%;
position: absolute;
left: 10%;
bottom: 50px;
}
}
}
.fade-enter-active, .fade-leave-active {
transition: opacity .2s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>
然后在 App.vue 中使用
<template>
<div id="app">
<Dialog title="自定义弹窗组件" />
</div>
</template>
<script>
import Dialog from "components/Dialog/Dialog";
export default {
name: 'app',
components: {
Dialog,
}
}
</script>
这个时候如果运行项目,页面是这个样子(确认按钮我使用了 elementUI 哦)
首先导入组件,然后使用 components
注册组件,最后在 template
中使用组件,这样我们就成功完成了一个组件的创建和使用。这种方式是我迄今为止使用最多的方式,当然也能够满足大部分的需求了。
但是有的情况下这种事不够用的,比如一些用于通知的弹框或者交互的组件,这种组件平时是不显示的,只有触发了某些动作的时候才会显示,如果我们还使用这种方式的话,就要先引入组件,然后再注册,最后在页面中使用,并且我们还要控制组件的显示与否,是不是增加很多无用的变量和代码,那么我们能不能用更简单方便的方式来创建组件呢?答案肯定是有的,往下看。
方法调用式创建
使用过像 elementUI
和 iview
之类的第三方组件库的同学可能都知道,他们提供了很多全局的 API 可以很方便的创建组件,比如弹出框等,他们是怎么做的呢,我们下面就来看看。还是接着上面的弹框组件写。
首先稍微改动一下刚才写的组件,加一些事件(关闭和确认)进去
<template>
<transition name="fade" v-if="isShow">
<div class="dialog-page">
<div class="dialog-box">
<h3 class="title">{{ title }}</h3>
<span class="close" @click="close">X</span>
<el-button type="primary" class="confirm-btn" @click="confirm">确定</el-button>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "Dialog",
props: {
title: {
type: String
}
},
data() {
return {
isShow: false
}
},
mounted() {
window.addEventListener('keyup', this.close)
this.isShow = true;
},
methods: {
close() {
this.isShow = false;
// 这里使用$nextTick是因为我们使用了过渡效果,关闭的时候先过渡,再销毁
this.$nextTick(() => {
this.$emit('close')
})
},
confirm() {
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
/* 和上面一样,太长不写了 */
...
</style>
新建文件
import Vue from "vue";
import Dialog from "@/components/Dialog/Dialog";
let vm;
const noop = () => {};
function createVNode(resolve = noop, reject = noop, props) {
return new Vue({
mixins: [
{
mounted() {
document.body.appendChild(this.$el);
},
beforeDestroy() {
document.body.removeChild(this.$el);
},
},
],
beforeDestroy() {
vm = null;
},
methods: {
confirm() {
resolve("点击确认");
this.$destroy();
},
close() {
reject("点击关闭");
this.$destroy();
},
},
render(createElement) {
return createElement(Dialog, {
on: {
confirm: this.confirm,
close: this.close,
},
props,
style: {
color: "red",
},
});
},
});
}
function show(props) {
return new Promise((resolve, reject) => {
// 如果有vm这个实例,先销毁
if (vm) {
vm.$destroy();
}
vm = createVNode(resolve, reject, props);
vm.$mount();
});
}
export default show;
倒着看,我们导出了一个 show
方法,它返回的是一个 Promise
,这就是说我们在调用这个方法的时候可以用 .then
的语法在未来某一个时刻(关闭弹框,或者点击确认)执行一些操作。
接着我们创建了一个 vm 实例,调用了 $mount
,是不是看着比较熟悉,没错,在 main.js
文件中
...
new Vue({
...
render: h => h(App),
}).$mount('#app');
它把 new vue
的生成的虚拟 DOM 转换成真实 DOM 挂载到了 #app
上面,那上面我们的 $mount
没有传参数,会挂载到哪里呢?其实是需要我们手动挂载的,这个就给个传送门吧。
接着说我们的组件,这里我们又看到一个createVNode
方法,不用说,这个就是用来创建组件的方法了,可以看到它返回了一个Vue
的实例化对象(虚拟 DOM),new Vue
的参数混入了一些生命周期函数,和两个methods
方法,其中在组件渲染完成后手动把当前组建的 DOM 插入到body
中,在组件销毁前移除掉。
最重要的还是下面的render
函数,这个内容很多,就不展开说了(我自己也是一知半解,说不好 🤔),想深入了解可以单击传送门,可以简单理解render
可以生成VNode
,就是虚拟节点。它的第一个参数是刚才我们编写的组件,第二个参数是一个对象,可以定义一些参数,比如要传给组件的参数、样式还有监听的事件等。
这样我们new Vue
的时候,没有用template
的方式,而是用了render
函数来生成我们要的组件,手动挂载到 DOM 中去(不在#app
里面,是并列的关系,都在body
下面)。
接下来我们就可以在需要的地方调用了,比如App.vue
点击按钮的时候,我们调用导入的 Dialog
方法,传了 title
参数,现在我们可以看看效果
到现在我们已经完成了组件的创建和调用,而且我们点击 关闭 和 确认 的时候可以看看控制台,是不是分别打印了
这就是我们在 App.vue
里面调用 Dialog
方法的时候分别在 then
和 catch
里面打印的,回到上面 /components/Dialog/index.js
文件中的 show
方法,我们返回的是promise
,而且我们把resolve
和reject
两个参数传入了 createVNode
方法中,分别在 methods
中的两个对应确认和关闭的方法中别调用,那么 confirm
和 close
是怎么被触发的呢
可以看 /components/Dialog/Dialog.vue
文件中,我们在点击的时候,使用了 $emit
来触发对应的事件,那么事件是在哪里被接收的呢?回到 /components/Dialog/index.js
文件中的 render
函数,我们在 createElement
方法的第二个参数中有一个 on
对象,是不是感觉很熟悉,没错,就是用来监听事件的,我们上面用 $emit
触发的事件也是在这里监听的。这样我们就在点击 确认
或者 关闭
后,执行 resolve
或者 reject
方法,然后就可以在 then
和 catch
中执行相应的操作了。
注册全局插件
到上面为止,我们已经可以很方便的以 API 的形式来创建组件,但是可能有人会觉得还不够方便,每次还要先导入,再调用,有没有更方便的方式呢?肯定是有的,现在我们就来试试。
其实也很简单,只需要把刚才的代码稍微改一下就可以了
...
- export default show;
+ export default {
+ install(vue) {
+ vue.prototype.$Dialog = show;
+ }
+ }
// main.js
+ import Dialog from '@/components/Dialog/index'
+ Vue.use(Dialog);
看到 Vue.use
又有了熟悉的感觉吧,我们平常使用插件不就是这么用的嘛!没错,第三方的插件能够以 Vue.use
的方式注册,是因为他们都在插件里面导出了一个 install
方法,不清楚的看这里哦 Vue.use。
这样我们就可以在需要的地方使用 this.$Dialog
调用了,也不需要先导入了,因为方法已经被挂到了 Vue
的原型上。
现在就来试一下
效果和刚才是一样的,就不贴图了,到这里是不是觉得和 ElementUI
提供的一些方法有点相似呢,没错,其实他们也是这么做的。
结语
本文主要介绍了几种创建组件的方式,具体要用哪一种还看根据具体的业务场景来选择,正所谓没有最好的技术方案,只有最合适的。但是说归说,多掌握一点总是好的,后面的内容就涉及到Vue
中比较难掌握的内容,比如render
、createElement
、$mount
等。其实这些东西更接近底层,通过学习这些我们可以更深刻的理解Vue
,而不只是停留在“会用”的阶段。
好了,就先说到这里,喜欢或者觉得有用可以点赞 + 收藏哦,有错误之处也欢迎指出!!!
刚封装了一个 Vue 图片预览的插件(vue-preview-imgs
)也是我封装的第一个插件,还有很多不足,欢迎一起交流,喜欢给个 star 哦!地址:https://github.com/hzpeng57/vue-preview-imgs