RadiomM
文章22
标签13
分类1
如何写一个函数式组件(vue2,vue3版)

如何写一个函数式组件(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)
/** 保存Loading构造实例 */
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 || {}
/** 实现功能点2 */
if(typeof options === 'string') {
options = {
text: options
}
}
/** 合并options跟data的属性 */
instance = new LoadingConstructor({
data: options
})
/** 挂载在VNode上面 */
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
/** 导入的是index.js的Loading */
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,
}
}
/** 创建vnode */
instance = createVNode(Main, props)
/** 渲染成到容器 */
render(instance, container)
/** container.firstElementChild:实际上我们只挂在了Loading组件 */
document.body.appendChild(container.firstElementChild)
/** 展示loading */
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等等,套路都一样,实现起来也没啥难度,文章到此为止。