如何写一个函数式组件(vue2,vue3版)
最近迷上了函数式组件,主要是最近接到一个需求,就是公司的产品原来的loading效果都是用uniapp提供的方法,所以这次希望全部统一一种loading效果。不过这次就不带uniapp框架的限制,只考虑vue2、vue3怎么实现一个函数式组件。
准备
这次实现的组件是loading组件,实现的功能主要下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| /** 1、能直接调用 */ Loading()
/** 2、能直接传入文字调用 */ Loading('加载中...')
/** 3、有相关配置调用 */ Loading({ text: '加载中...', mask: true })
/** 4、关闭实现 */ Loading.close()
|
为了方便使用,可以直接挂载在Vue的实例里面,但是Vue2,Vue3的方式不太相同,实现也不太相同,所以下面会分开实现。
vue2
首先是展示vue文件,这里不做过多的解释,这种程度难不了大家。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <template> <div class="loading-container" :class="{ pe: !mask }" v-if="visible"> <div class="loading-wrapper" :class="{ mask: mask, pe: !mask }"> <img src="https://zhanchi-static.oss-cn-shenzhen.aliyuncs.com/zhancchi_recruit/loading.gif" /> <div class="loading-text">{{ text }}</div> </div> </div> </template> <script> export default { name: "globalLoading", data() { return { text: "加载中", visible: false, mask: false }; }, methods: { close() { this.visible = false; } }, watch: { visible(newVal) { this.visible = newVal; } } }; </script> <style lang="scss" scoped>
.pe { pointer-events: none; } .loading-container { position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 999;
.loading-wrapper { position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 999; display: flex; flex-direction: column; align-items: center; justify-content: center;
img { width: 32px; height: 32px; }
.loading-text { display: inline-block; max-width: 100px; margin-top: 5px; font-size: 12px; color: #666 } }
.mask { background: rgba(0, 0, 0, 0.5); .loading-text { color: #fff; } } } </style>
|
大家可以根据自己的需求设计,下来就是怎么改成函数式组件。
首先,我们需要Vue2里面给我提供的一个方法——extend。给予我们可以构建“子类”的办法,接着创建index.js文件。
1 2 3 4 5
| import Vue from 'vue'; import Main from './index.vue' let LoadingConstructor = Vue.extend(Main)
let instance
|
接着我们需要Loading方法,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const Loading = function(options) { options = options || {} if(typeof options === 'string') { options = { text: options } } instance = new LoadingConstructor({ data: options }) instance.$mount() document.body.appendChild(instance.$el) }
|
在上面将options给data赋值时,会将属性逐一合并,由于一开始我们设置了初始值,所以没有传options的时候,会默认使用组件设置的初始值。那么实现关闭loading的方法就简单了:
1 2 3 4 5
| Loading.close = function() { instance && (instance.visible = false) }
export default Loading
|
接着只需要在Vue实例挂载即可
1 2
| Vue.prototype.$Loading = Loading
|
vue3
其实vue3的实现大同小异,只不过由于vue3写法等方面不一样了,所以实现方式也会有不一样。先看template文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| <script setup> import { ref } from 'vue'
defineProps({ text: { type: String, default: '加载中...', }, mask: { type: Boolean, default: false, }, })
const visible = ref(false)
function open() { visible.value = true }
function close() { visible.value = false }
defineExpose({ close, open, }) </script> <template> <div v-show="visible" class="loading-container" :class="{ pe: !mask }"> <div class="loading-wrapper" :class="{ mask: mask, pe: !mask }"> <img src="https://zhanchi-static.oss-cn-shenzhen.aliyuncs.com/zhancchi_recruit/loading.gif" alt="" /> <span class="loading-text">{{ text }}</span> </div> </div> </template> <style lang="scss" scoped> .loading-container { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 999;
.loading-wrapper { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 999; display: flex; flex-direction: column; align-items: center; justify-content: center;
img { width: 32px; height: 32px; }
.loading-text { display: inline-block; max-width: 100px; margin-top: 5px; font-size: 12px; color: #666; } }
.mask { background: rgb(0 0 0 / 50%);
.loading-text { color: #fff; } } }
.pe { pointer-events: none; } </style>
|
在vue2中,我们可以将参数定义在data,通过构造器合并属性的方式来传递参数,但是在vue3中已经不提供这样的方法了,所以需要将参数定义为props,并且通过defineExpose暴露方法在外部使用(其实直接修改visible值也可以,但是使用者来说,还是希望更语义化点吧)。
接着是index.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { createVNode, render } from 'vue' import Main from './index.vue'
let instance const container = document.createElement('div') const Loading = options => { if (instance) { document.body.removeChild(container.firstElementChild) } let props = options || {} if (typeof options === 'string') { props = { text: options, } } instance = createVNode(Main, props) render(instance, container) document.body.appendChild(container.firstElementChild) const vm = instance.component vm.exposed.open() }
Loading.close = () => { if (instance) { instance.component.exposed.close() } }
export default Loading
|
不同于vue2挂载在实例的方法,vue3是下面方式挂载,但调用就是少写个this罢了(因为vue3没有this概念)
1 2 3 4 5 6
| import { createApp } from 'vue' import App from './App.vue' import Loading from '@/component/loading/index.js'
const app = createApp(App) app.config.globalProperties.$Loading = Loading
|
总结
至此就完成了函数式组件的写法,当然这是Loading组件只展示的了最简单实现,比如你可以添加一些回调函数,如:success,faild等等,套路都一样,实现起来也没啥难度,文章到此为止。