前言
平常我们写页面时基本上都要用到组件库,那你知道它们是如何封装实现的吗。继上次我们就试着了解下ElementUI的通用组件如何打造的,具体见 如何用VUE3打造通用组件 - 掘金 (juejin.cn)。接下来我们仿照着vue3的一个Element Plus框架中的Notification组件------实现比较基础的弹出框组件,打造出VUE公用弹出框组件!
先看效果图 
准备
- 提前配置好环境
 - 创建好目录
 

- 下载
Element Plus包(主要是为了方便可以用到相应的样式,这样就不用手写考虑组件的css样式,所以在html文件中写的类名是规定好的,改动的话就不能用组件库里的样式,或者自行写样式也行) 
            
            
              csharp
              
              
            
          
          yarn add element-plus
        - 在
main.js中引入Element Plus 

正文
大致思路
- 基于在点击按钮后弹出相应的信息框------主要调用一个弹出框函数,传入相应的参数即可实现
 - 打造弹出组件:首先搭建基本框架 ,可以放标题、内容以及关闭按钮,然后把整个结构用
 teleport包裹住,放到body标签下(毕竟弹出框最好在全局生效),以及控制好组件之间的数据传递- 实现控制弹出框的位置:水平、垂直、偏移的位置。
 - 实现js动态渲染的效果,这样可以通过调用一个函数并传入相应的值实现(加载notification.vue文件并渲染到页面)
 - 主要是将vue组件通过vue中的h函数 ,编译成 vnoode,再通过 vue 中的render函数将vnode 渲染成真实的DOM,再将真实DOM添加到body标签下
 
一、编写notification.vue文件(实现弹出框主文件)
- 搭建基本框架
 
            
            
              xml
              
              
            
          
          <template>
  <teleport to="body">
      //动态绑定位置的样式、不同类型弹出框的类名
    <div class="el-notification" :style="positionStyle" :class="typeClass">
    <div class="el-notification__group">
      <h2 class="el-notification__title">{{title}}</h2>
      <div class="el-notification__content">
        <p>{{message}}</p>
      </div>
    <button class="el-notification__close-button" @click="onCloseHandler">X</button>
    </div>
  </div>
  </teleport>
</template>
<style lang="css">
.el-notification__close-button{
  position:absolute;
  top:8px;
  right:15px;
  cursor:pointer;
  font-size:12px;
}
</style>
        tips:
{{title}}、{{message}}是从props.js文件中接收过来的值teleport:由于要打造成公用的弹出框组件,那么组件应该在全局生效,应该出现在body标签下,所以这时候用到teleport------它是一个内置组件,可以将一个组件内部的一部分模块传送到该组件的DOM结构外层的位置去,也就是可以指定将其渲染到某个位置。
- 编写js
 
            
            
              xml
              
              
            
          
          <script> 
  import { ref,computed } from 'vue'
  import { notificationProps } from './props.js'
  export default {
    props:notificationProps,
    setup(props){//props代表的是子组件接收到父组件传的参数
      const visible=ref(true) //组件自己控制是否展示
      const vertialOffsetVal=ref(props.offset)//垂直偏移位置初始值
      const typeClass=computed(()=>{//动态添加不同类型的弹出框的类名,为了后面可以给不同类型的弹出框展示不同的效果
        return props.type?`el-icon-${props.type}`:''
      })
      const horizationalClass=computed(()=>{//水平方向上的位置
        return props.position.endsWith('right')?'right':'left'
      })
      const verticalProperty=computed(()=>{//竖直方向上的位置
        return props.position.startsWith('top')?'top':'bottom'
      })
      const positionStyle=computed(()=>{ //偏移的距离
        return {
          [verticalProperty.value]:`${vertialOffsetVal.value}px`,
          [horizationalClass.value]:'0px'
        }
      })
      
      const onCloseHandler=()=>{//关闭弹出框
        let p=document.querySelector('.el-notification')
        document.body.removeChild(p) //从body中移除指定的弹出框dom结构
      }
      return {
        typeClass,
        positionStyle,
        verticalProperty,
        horizationalClass,
        vertialOffsetVal,
        visible,
        onCloseHandler
      }
    }
  }
</script>
        二、编写props.js文件(实现父组件传递的参数情况)
在notification.vue中将子组件接收到父组件传递的参数有哪些以及其类型 和默认值抽离出来,写在此文件中(在这里就写了其中一些参数类型)。
            
            
              go
              
              
            
          
          export const notificationProps = {
  title: { type: String, default: '' },
  message: { type: String, default: '' },
  position: { type: String, default: 'top-right' },//默认位置是在右上角处
  offset: { type: Number, default: 0 },
  type: { type: String, default: '' }
}
        至此基本上就可以实现以组件的形式展示弹出框的效果。但是由于Element plus中使用时是调用一个函数,并且传入参数才实现,接下来我们考虑把notification.vue转换为js文件,并以函数的形式抛出。
三、编写index.js文件(将notification.vue转换为js文件,编写成函数形式)
- 思路 :将vue组件通过
vue中的h函数 ,编译成 vnoode,再通过 vue 中的render函数将vnode 渲染成真实的DOM,再将真实DOM添加到body标签下。 
- 编写可以直接调用的函数实现弹出框,并将其抛出。
 
            
            
              javascript
              
              
            
          
          import { isVNode,h,render } from 'vue' //isVNode判断是否为vue的DOM结构
import notificationComponent from './notification.vue'
export function Notification(options){
  //加载notification.vue文件并渲染到页面
  return createNotification(options) //封装成另一个函数实现
}
        - 实现
createNotification函数 
            
            
              scss
              
              
            
          
          function createNotification(options){//用js来创建组件
  const instance=createNotificationByOpts(options)//将参数的值进行融合
  addToBody(instance)//添加到body标签下
  return instance.proxy //vue编译的时候就存在proxy属性
}
function addToBody(instance) {
  document.body.appendChild(instance.vnode.el)
}
        - 实现
createNotificationByOpts函数 
            
            
              scss
              
              
            
          
          function createNotificationByOpts(opts){
  if(isVNode(opts.message)){//如果所传参数的message是虚拟节点的时
    return createComponent(notificationComponent,opts,()=>opts.message)
  }
  return createComponent(notificationComponent,opts)
}
        - 实现
createComponent函数(核心) 
            
            
              javascript
              
              
            
          
          function createComponent(Component,props,children){//将vue组件变成js组件
  const vnode = h(Component, {...props, ref: 'el_component'}, children)// 编译 将vue组件转换为虚拟DOM结构
  const container = document.createElement('div')
  vnode[Symbol('el_component_container')] = container //给vnode创建一个独一无二的key,并赋值为空的div容器
  render(vnode, container) //渲染 通过render渲染为真实的DOM
  return vnode.component //返回真实的DOM结构
}
        index.js全部代码
            
            
              javascript
              
              
            
          
          import { isVNode,h,render } from 'vue' //判断是否为vue的DOM结构
import notificationComponent from './notification.vue'
export function Notification(options){
  //加载notification.vue文件并渲染到页面
  return createNotification(options)
}
function createNotification(options){//用js来创建组件
  const instance=createNotificationByOpts(options)
  addToBody(instance)
  return instance.proxy //vue编译的时候就存在proxy属性
}
function createNotificationByOpts(opts){
  if(isVNode(opts.message)){//如果所传参数的message是虚拟节点的时
    return createComponent(notificationComponent,opts,()=>opts.message)
  }
  return createComponent(notificationComponent,opts)
}
function createComponent(Component,props,children){//将vue组件变成js组件
  const vnode = h(Component, {...props, ref: 'el_component'}, children)// 编译 将vue组件转换为虚拟DOM结构
  const container = document.createElement('div')
  vnode[Symbol('el_component_container')] = container //给vnode创建一个独一无二的key,并赋值为空的div容器
  render(vnode, container) //渲染 通过render渲染为真实的DOM
  return vnode.component //返回真实的DOM结构
}
function addToBody(instance) {
  document.body.appendChild(instance.vnode.el)
}
        至此,一个以函数方式引入组件的弹出框组件就打造好了,现在我们来使用试下吧!
四、实践
在App.vue文件下编写以下代码:
            
            
              xml
              
              
            
          
          <template>
  <div>
    <button @click="openNotify">点击弹出Notification</button>
  </div>
</template>
<script setup>
import {Notification} from './components/Notification/index.js'
const openNotify=()=>{
      Notification({
        title:'标题',
        message:'渲染信息',
        position:'bottom-right',
        offset:50,
      })
  }
</script>
<style lang="css">
body{
  background:pink;
}
</style>
        效果图:

最后
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正 。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤。要是您觉得有更好的方法,欢迎评论,提出建议!