序言
在日常拧螺丝的时候经常会用到
this.$message.success('XXXX')来提示用户当前的状态,这种JS创建组件的方式还是很新颖的,我就在想他是咋做的。我能不能用这种方式自己也搞个更加符合自己需求的$alert
思考过程
在原生的js里我们可以调用BOM方法中的alert方法来生成一个阻断式的弹框,但是这种弹框太过于简陋,而且影响用户的下一操作。
elementUI中就给我们提供了一个很好的组件$alert
<el-alert title="成功提示的文案" type="success">
</el-alert>
- 每个使用的地方,都得注册组件;
- 需要预先将
<Alert>放置在模板中; - 需要额外的 data 来控制 Alert 的显示状态;
- Alert 的位置,是在当前组件位置,并非在 body 下,有可能会被其它组件遮挡。
所以在特定的情况下我们就得想办法让他可以类似
this.$message.success('XXXX')的方式动态生成组件。
解决思路
要想不在页面上以写死标签形式注入组件,就得用到动态生成组件的方式,我了解到的方式有两种
- 使用
$extend+$mount的方式 - 使用
render+$mount的方式 两种方式各有优劣: $extend的方式更像是在写一个组件 但是其中的template模板只能使用字符串的方式来写了(正则匹配文件的方式 这种魔道手段不算哈~)render渲染函数的方式 可以直接拿一个vue文件来用,坏处是语法没有$extend好理解
废话不多说 直接上代码感受下这两种方式的差异
// $extend方式
/**
* 第一种是$extend $mount()
* 这种方式是基于模板创建一个子组件
* 然后把生成的子组件 调用$mount完成初始化
* 最后把生成好的子组件dom(每个生成好的子组件都有dom节点:$el)挂载到指定的dom下面
* 这里的需求是挂载到body下面
*/
import Vue from 'vue';
const newAlert=Vue.extend({
template:'<div id="123">{{messgae}}</div>',
data(){
return {
messgae:'你好啊!!!!!!'
}
}
})
document.body.appendChild( newAlert().$mount().$el)
// render方式
import Vue from 'vue';
import hello from './hello.vue' //这里需要放一个自己的vue文件 代码省略了哈
const props={
name:'我是另外一种render方式 渲染的组件!!!'
}
const render=new Vue({
data(){
return {
}
},
render(h){
h(hello,{
props
})
}
})
const instance=render.$mount()
document.body.appendChild( instance.$el)
具体实现
前置的知识扯完了,我们来真正的实现$alert吧
首先我们先要明确我们要以什么样的方式来调用$alert:
this.$myAlert.info({
content:'内容',
duration:2
})
我们想要以这种方式调用$alert那么我们就要在main.js中的Vue.prototypevue的原型链上注册下这个方法,这样我们才能通过this的方式全局调用。
// main,js
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import Alert from '../src/components/myAlert/index' //引入
Vue.config.productionTip = false
Vue.use(ElementUI);
Vue.prototype.$myAlert=Alert //挂载
new Vue({
render: h => h(App),
}).$mount('#app')
接着我们就要写 alert组件的入口文件index.js了
// index.js
import alertRender from './alertRender'
let instance=null
// 获得render组件的单例
function getSingleRender(){
instance=instance||alertRender
return instance
}
// 初始化render
function initRender({content='',duration=1}){
const render=getSingleRender()
return render.add({content ,duration })
}
export default {
// 这里留给外部调用不同的类型alert
info(option){
initRender(option)
}
}
入口函数主要干的事情 return出去一个外界可调用的函数 该函数接受调用方传递的值后 传递给渲染函数 ,达到自定义弹窗的目的
接下来开始写主要的代码块:render函数 在同级目录下新建一个alertRender.js
//alertRender.js
import Vue from 'vue'
import basePage from './basePage.vue' //引入模板vue
const render =new Vue({
data(){
return {}
},
render(h){
return h(basePage,{
props:{}
})
}
})
const instance =render.$mount()
document.body.appendChild(instance.$el)
const child=instance.$children[0] //由于render出来的组件只有一个 所以无脑$children[0]就行了
export default {
//1 首先为外界暴露一个新增方法
add(option){
child.add(option)
}
}
最后我们把模板vue撸出来就大功告成了!
<template>
<div class="alert">
<div v-for="(item) in notices" :key="item.name" class="alert-main">
<div class="alert-content"> {{item.content}}</div>
</div>
</div>
</template>
<script>
let seed=0;
function getUUid( ) {
return 'alert_'+(seed++)
}
export default {
data(){
return {
notices:[]
}
},
methods:{
add(item){
const name=getUUid();
let _notice=Object.assign({name},item)
this.notices.push(_notice)
//定时移除
const duration=item.duration
setTimeout(()=>{
this.remove(name)
},duration*1000)
},
remove(name){
const _index= this.notices.findIndex(item=>{
return item.name === name
})
if(_index>-1) return this.notices.splice(_index,1)
}
}
}
</script>
<style scoped>
.alert{
position: fixed;
width: 100%;
top: 16px;
left: 0;
text-align: center;
pointer-events: none;
}
.alert-content{
display: inline-block;
padding: 8px 16px;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 6px rgba(0, 0, 0, .2);
margin-bottom: 8px;
}
</style>
模板有一个小难点就是add方法 接受到外界的值之后把这个值插入到list中 再定时清除掉
好的 那我们测试下~
this.$myAlert.info({
content:'这是自定义弹窗111',
duration:1
})
this.$myAlert.info({
content:'这是自定义弹窗11221',
duration:2
})
结语
你不会认为
alert组件就这么简陋吧。。。不会吧~是的!我们还可以改造模板vue,通过传入按钮flag等来改变状态 甚至还可以传入一些render函数来生成一些特定的效果。大佬你是了解我的。。。我在里面埋了很多可以升级的坑位,比如data props等等 等我柠完螺丝有空了再来优化一版吧