vue中this.$message的实现

  目录

vue中全局message组件的实现

vue3中的$message

element plus中message组件可以this.$message来调用,参考它实现一个简版的message组件

message.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
80
81
82
83
84
85
86
87
88
89
90
91
<template>
<transition
@before-leave="onClose"
@after-leave="$emit('destroy')"
>
<div
v-show="visible"
:id="id"
:style="customStyle"
class="message-wrap"
@mouseenter="clearTimer"
@mouseleave="startTimer"
>
<slot>
{{ message }}
</slot>
</div>
</transition>
</template>
<script>
import { defineComponent, defineProps, computed, ref, onMounted, onBeforeUnmount, watch, getCurrentInstance } from 'vue'
export default defineComponent({
name: 'message',
props: ['message', 'id', 'offset', 'onClose', 'duration'],
emits: {
destroy: ()=> true
},
setup(props) {
const visible = ref(true);
let timer = null;
onMounted(()=> {
startTimer();
});
onBeforeUnmount(()=> {
document.removeEventListener('keydown', keydown, false);
});
// 开始定时器
const startTimer = ()=> {
// 设置定时关闭
timer = setTimeout(()=> {
visible.value = false;
}, props.duration || 5000);
}
// 清除定时器
const clearTimer = ()=> {
clearTimeout(timer);
}
// 样式计算属性
const customStyle = computed(()=> {
return {
top: props.offset+'px'
}
});
// 案件按下回调函数
const keydown = ({ code })=> {
if(code === 'Escape') {
if(visible.value) {
visible.value = false;
}
}
}
// 添加事件
document.addEventListener('keydown', keydown, false);

return {
visible,
customStyle,
startTimer,
clearTimer
}
},
})
</script>
<style lang="less" scoped>
.message-wrap {
position: fixed;
width: 200px;
margin: 0 auto;
padding: 10px 10px;
color: rgb(233, 110, 110);
border: 1px solid rgb(243, 122, 122);
background-color: rgba(243, 122, 122, .1);
border-radius: 5px;
top: 10px;
left: 0;
right: 0;
font-size: 13px;
user-select: none;
cursor: auto;
}
</style>

这里用到了vue自带的transition组件,这里使用了before-leave和after-leave两个钩子

message.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
36
37
38
39
40
41
42
43
44
45
import { createVNode, render } from 'vue';
import MessageConstructor from './message.vue';
const instances = [];
let seed = 1;
const message = (options={})=> {
let verticalOffset = 10;
if(typeof options === 'string') options = { message: options }
// 用于销毁vm,也就是从dom中删除
options.onDestroy = ()=> {
// console.log('destroy已经调用');
render(null, container);
}
options.onClose = ()=> {
delInstance(options.id);
}
options.id = seed++; // 设置id
// 计算高度
instances.forEach(vm=> {
verticalOffset += (vm.el.offsetHeight + 10);
});
options.offset = verticalOffset;
const vm = createVNode(MessageConstructor, options, options.message);
instances.push(vm);
const container = document.createElement('div');
render(vm, container);
document.body.appendChild(container.firstChild);

}
// 销毁的实例从instances中删除,并重新计算offset
// 这里需要注意,一定在transition的before-leave回调中执行
const delInstance = (id)=> {
let idx = instances.findIndex(vm=> {
return id === vm.component.props.id;
});
const curVm = instances[idx];
const removedHeight = curVm.el.offsetHeight;
// debugger;
const len = instances.length;
for(let i=idx; i<len; i++) {
const pos = parseInt(instances[i].el.style.top, 10) - removedHeight - 10;
instances[i].component.props.offset = pos;
}
instances.splice(idx, 1);
}
export default message;

这里是全局调用$message时的逻辑,全局创建和销毁组件。

index.js

1
2
3
4
5
import message from './message';
message.install = (app) => {
app.config.globalProperties['$message'] = message;
}
export default message;

对外导出,在main.js中引入

1
2
3
4
import Message from './components/message/index';
let app = createApp(App);
app = app.use(Message);
app = app.mount('#app');

使用$message

vue3的setup函数中使用

1
2
3
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
proxy.$message('message组件');

总结

以上是vue3中全局调用组件的实现,vue2的实现也差不多,只是在创建组件,挂载,销毁时的api写法不一样,其它基本一致。
vue3-message
vue2-message

vue3中使用provide,inject

上面使用的是app.config.globalProperties['$message'] = message;这种方法,但是,vue3最新的api中已经淘汰了这种方法,建议使用provide,inject
下面是一个新的例子,vue-router也是这么实现

定义插件

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
import { inject } from 'vue'
const watermark = {
set: () => {
   console.log('set watermark')
},
 show: () => {
   console.log('show watermark')
},
 hide: () => {
   console.log('hide watermark')
},
 clear: () => {
   console.log('clear watermark')
},
 install (app, options) {
   const watermark = this
   app.config.globalProperties.$watermark = watermark
   app.provide('watermark', watermark)
}
}

const useWaterMark = function () {
 return inject('watermark')
}

export {
 watermark,
 useWaterMark
}

调用插件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
 <div>
   demo
 </div>
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { getCurrentInstance } from 'vue'
import { useWaterMark } from './plugin/watermark.js'

const watermark = useWaterMark()
watermark.hide()
</script>

vue2中的$loading

loading.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
<template>
<div id="loading-wrap" v-if="loading">
<img src="/images/loading.gif" alt="">
<span>{{ loadText }}</span>
</div>
</template>
<script>
export default {
data() {
return {
loading: true
}
},
props: ['loadText'],

}
</script>
<style lang="less">
#loading-wrap {
background-color: rgba(0,0,0,.7);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
img {
flex-grow: 0;
width: 100px;
}
}
</style>

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import loadVue from './loading.vue';
export default {
install(Vue) {
let vEl;
Vue.prototype._showloading = function(options = {}) {
vEl = new Vue(Object.assign({}, loadVue, {
propsData: {
loadText: '加载中...'
}
}))
document.body.appendChild(vEl.$mount().$el);
}
Vue.prototype._hideloading = function() {
document.body.removeChild(vEl.$mount().$el);
}
}
}