猴子都能看懂动态生成组件:$alert

1,455 阅读3分钟

序言

在日常拧螺丝的时候经常会用到this.$message.success('XXXX')来提示用户当前的状态,这种JS创建组件的方式还是很新颖的,我就在想他是咋做的。我能不能用这种方式自己也搞个更加符合自己需求的$alert

aa.png

思考过程

在原生的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
      })

QQ录屏20220409233518202249233661.gif

结语

你不会认为alert 组件就这么简陋吧。。。不会吧~是的!我们还可以改造模板vue,通过传入按钮flag等来改变状态 甚至还可以传入一些render函数来生成一些特定的效果。大佬你是了解我的。。。我在里面埋了很多可以升级的坑位,比如data props等等 等我柠完螺丝有空了再来优化一版吧