引言
在我们的日常开发中,我们一般会引入做的比较成熟的第三方UI框架,比如ElementUI。
当我们在调用UI框架中的组件时,会发现常用的调用方式有两种,一种是直接在页面中嵌入组件:
代码语言:javascript复制<el-dialog
v-model="dialogVisible"
>
...
</el-dialog>
另一种则是在js中通过方法调用:
代码语言:javascript复制ElMessageBox.confirm(...)
可以看到ElementUI中的组件使用方式更加灵活,可以满足不同场景的需求,那么这两种不同的调用方式都是如何实现的呢?下面我们就在弹框组件的基础来实现一下。
全局组件注册
我们在home中引入了Modal弹框,才能在home中使用:
代码语言:javascript复制...
<script setup>
import Modal from '../components/Modal';
</script>
像弹框这种在项目中会被频繁使用到的公共组件,每次都在使用的地方引入无疑是很不方便的,所以我们可以将弹框组件注册为全局组件,在main.js中,我们添加如下代码。
代码语言:javascript复制...
import Modal from './components/Modal/Modal.vue';
const app = createApp(App);
app.component('Modal', Modal);
app.use(router).mount('#app');
相对于Vue2来说,Vue3中注册全局组件,不再将组件挂载到Vue对象上,而是应该挂载到createApp生成的实例上,所以,如果我们有多个app实例的情况:
代码语言:javascript复制import Modal from './components/Modal/Modal.vue';
const app1 = createApp(App1);
const app2 = createApp(App2);
// 只有app1注册了组件
app1.component('Modal', Modal);
app1.mount('#app1');
app2.mount('#app2');
如上的代码中,只有app1实例下才挂载了弹框组件,app2不会受到影响。
这样我们在app下的任何组件中,都可以直接使用我们的弹框组件而不需要再额外引入了。
函数式组件
除了嵌入的方法外,函数式调用的方法也是比较常见的,而且相对于嵌入式来说,函数调用的时候可以将组件绑定到body元素下,避免了组件的样式被其他地方所覆盖,从而可能导致组件展示错乱,我们的弹框组件与app组件在同一级渲染。
在Modal文件夹下新建index.js文件,接下来我们就在index.js文件中来实现下弹框组件的函数式调用。
代码语言:javascript复制// 弹框的函数式调用方法
function openModal () {
// 1. 创建弹框组件实例
// 2. 创建渲染节点
// 3. 将实例挂载到页面节点上
}
export default openModal
我们定义了一个openModal的方法,并且梳理下这个方法中要实现的逻辑,最后导出了这个方法供其他地方调用。
我们都知道在Vue3中是通过createApp来创建一个组件实例的,所以我们引入createApp和Modal.vue,创建一个弹框组件实例。
代码语言:javascript复制import { createApp, provide } from 'vue';
import Modal from './Modal.vue';
function openModal () {
// 1. 创建弹框组件实例
const modalApp = createApp(Modal);
// 2. 创建渲染节点
// 3. 将实例挂载到页面节点上
}
我们在实现Modal组件的时候是有一些属性需要传递的。
代码语言:javascript复制<Modal
title="信息提示" // 弹框标题
content="打开了一个弹框" // 弹框内容
...
>
</Modal>
createApp的第二个参数就可以让我们传递参数给弹框组件的props属性。
代码语言:javascript复制function openModal (options = {}) {
const modalApp = createApp(Modal, {
// 控制弹框是否显示
modelValue: true,
// 传入弹框标题
title: options.title || 'title',
// 传入弹框内容
content: options.content || 'content',
// 传入关闭弹框的方法
close: () => {
}
})
}
调用openModal方法时,我们会传入options参数,携带弹框的标题,内容等信息,来赋值给组件的props属性。
接下来,我们继续实现创建渲染节点并将弹框实例挂载上去。
代码语言:javascript复制function openModal () {
// 1. 创建弹框组件实例
...
// 2. 创建渲染节点
const dom = document.createElement('div');
document.body.appendChild(dom);
// 3. 将实例挂载到页面节点上
modalApp.mount(dom);
}
这样我们就完成了弹框实例的挂载渲染,并且可以通过openModal方法进行函数式调用。
代码语言:javascript复制<template>
<button @click="open">打开弹框</button>
</template>
<script setup>
import openDialog from '../components/Modal';
const openDialog = () => {
openDialog({
title: '标题',
content: '内容'
});
}
</script>
我们在传递props参数的时候,还传入了close方法用来关闭弹框,那要怎么实现关闭弹框的方法呢?
其实简单来说,关闭弹框就是将弹框实例卸载下来,所以在close方法中,我们添加如下代码:
代码语言:javascript复制close: () => {
// 将弹框实例卸载
modalApp.unmount(dom);
// 删除页面节点
document.body.removeChild(dom);
}
修改下Modal.vue中的按钮点击事件:
代码语言:javascript复制const onOk = () => {
props.close();
}
通过DOM结构,我们可以看到,点击确定按钮时,弹框组件已经从DOM树中删除了。
到目前为止,我们使用组件的时候还是需要引入方法,为了方便全局使用,我们可以进一步优化,将openDialog方法注册到全局属性中去。
那么我们在什么时候完成注册的操作呢?当我们使用第三方插件的时候,经常会使用到app.use方法。
代码语言:javascript复制app.use(router).mount('#app');
其实use方法就是Vue提供给我们来注册插件的,use方法会先判断插件有没有被注册,如果没有注册,会调用插件的install方法,如果插件不是对象,本身就是个方法,那么就执行这个方法。
所以我们可以添加一个dialogInstall方法,在use执行的时候,将openDialog方法注册到全局。
代码语言:javascript复制function openModal () {
// 1. 创建弹框组件实例
...
// 2. 创建渲染节点
...
// 3. 将实例挂载到页面节点上
...
}
function dialogInstall (app) {
console.log('dialogInstall was invoked');
app.provide('OPENDIALOG', openModal)
}
export {
openModal,
dialogInstall
}
在main.js中引入dialogInstall方法并注册。
代码语言:javascript复制import {dialogInstall} from './components/Modal';
...
app.use(dialogInstall).use(router).mount('#app');
当我们刷新页面的时候,可以看到“dialogInstall was invoked”这句话在控制台中打印出来了,说明dialogInstall方法在注册的时候确实被执行了。
那么在其他组件中,我们使用inject获取到openModal方法进行调用。
代码语言:javascript复制const openDialog = inject('OPENDIALOG');
const open = () => {
openDialog({
title: '标题',
content: '内容'
});
}
我们这里是利用provide和inject来实现全局属性的传递,大家也可以使用app.config.globalProperties
来挂载openDialog方法,不妨自己去尝试实现一下。
自定义指令
除了不同的调用方式外,有的时候我们还希望给组件添加一些自定义的指令来完成一些特殊需求,丰富组件的功能。
在上一节的插槽模块中,我们介绍了怎么在弹框组件中传入表单内容,如果要求弹框组件显示的时候,表单内的输入框自动获得焦点,要怎么实现呢?
我们定义一个自定义指令v-focus来实现自动获得焦点的功能,在表单子元素中,给input输入框绑定v-focus指令。
代码语言:javascript复制<Modal
title="信息提示"
content="打开了一个弹框"
v-model="modalVisible"
>
<form>
<label>姓名: </label>
// 给姓名输入框增加v-focus指令
<input v-focus v-model="name" placeholder="请输入姓名" />
<label>年龄: </label>
<input v-model="age" placeholder="请输入姓名" />
</form>
</Modal>
指令已经添加了,接下来就是实现指令功能了,Vue3注册自定义指令也需要在app实例上进行,我们在main.js中添加v-focus的代码。
代码语言:javascript复制app.directive('focus', {
mounted(el) {
// el为指令绑定的页面元素
el.focus()
}
})
通过directive方法我们定义了一个指令,自定义指令时也支持钩子函数的调用,我们希望在表单元素加载完成后自动获得焦点,所以在mounted钩子中增加元素获得焦点的方法。
钩子函数支持多个参数mounted(el, binding)
,el表示指令绑定的DOM元素,binding则是指令的一些参数信息,目前弹框组件在页面位置是居中展示,如果我们希望可以自定义弹框组件的位置,就可以通过binding来传递参数。
我们定义一个新的指令v-position用来控制弹框的位置。
代码语言:javascript复制app.directive('position', {
mounted(el, binding) {
// 设置弹框的top位置
el.style.position = 'absolute';
el.style.top = binding.value 'px';
}
})
在Modal.vue中给弹框内容添加v-position指令,并携带位置参数。
代码语言:javascript复制<div class="modal-content" v-position="200">
...
<div>
可以看到,v-position指令传入了一个参数200,在mounted中,我们通过binding.value来获取传入的参数并赋值给元素的top属性。
参数也支持以对象的方式传入。
代码语言:javascript复制<div class="modal-content" v-position="{top: 200, left: 400}">
...
<div>
app.directive('position', {
mounted(el, binding) {
// 设置弹框的top位置
el.style.position = 'absolute';
el.style.top = binding.value.top 'px';
el.style.left = binding.value.left 'px';
}
})
binding除了参数,还可以获取到指令的属性,比如我们让弹框的top属性单独设置。
代码语言:javascript复制<div class="modal-content" v-position:["top"]="200">
...
<div>
app.directive('position', {
mounted(el, binding) {
// 设置弹框的top位置
el.style.position = 'absolute';
el.style[binding.arg] = binding.value 'px';
}
})
不管是arg还是value,都支持传入动态参数,这也让自定义指令的使用变得更加灵活。
总结
本小节中我们介绍了组件的全局注册,以及组件函数式调用的实现,在Vue2中,因为常规组件优化的不够完善,使用函数式组件初始化和渲染都很快,可以大幅度的提高页面的响应程度,提升性能,这也是函数式组件的主要应用场景。
在Vue3中,常规组件得到优化,函数式组件的性能提升已经很微小了,常规组件就足以满足日常的开发需求了。
然后我们进一步介绍了Vue3中如何去自定义指令,以及自定义指令相关的传参数方法,使用自定义指令可以辅助我们的组件实现更多更加复杂的功能。通过本节的学习,希望大家对组件的概念有个更清晰的理解,对组件的使用也可以变得更加灵活。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!