Vue3.0如何封装全局的Loading或者弹框,以及如何获取动态渲染的节点进行操作

5,335 阅读1分钟

首先Vue3.0封装全局的组件和2.0有所以不同,3.0采用createApp,reactive或者createVNode,render

这里采用createApp

image.png

1.先搞个loading.vue组件

<template>
  <section>
    <div class="box" v-if='options.showToast'>
      <div class="load">
        <img src="../assets/load.gif" alt="">
        <div class="load_txt">{{options.txt}}</div>
      </div>
    </div>
  </section>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
  options: {
    type: Object,
    default: {}
  }
})
</script>
<style scoped>
.box {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  width: 100%;
  height: 100%;
  /* background: rgba(0, 0, 0, 0.5); */
}

.load {
  width: 180px;
  height: 180px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -90px;
  transform: translate(-50%);
}
.load img {
  width: 100%;
}
.load_txt {
  text-align: center;
  color: #e4393c;
}
</style>

2.在utils文件夹中建load.js

  1. const app = createApp({})返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文

  2. 该函数接收一个根组件选项对象作为第一个参数

  3. 使用第二个参数,我们可以将根 参数 传递给应用程序:

import {createApp,reactive} from 'vue'
import Loading from '@/components/loading.vue'
const divDom = document.createElement('div')
divDom.setAttribute('class', 'loading-container')
// document.body.appendChild(divDom)
const options = reactive({
  showToast: true,
  txt: '拼命加载中...'
})

//这里是关键部位options 是向Loading 组件传递的参数
const $loading = createApp(Loading, {options}).mount(divDom)

const loadPlguin = {
  show({txt}) { // 控制显示loading的方法,这里是show方法传入的对象
    options.showToast = true
    options.txt = txt
    document.body.appendChild($loading.$el)
  },

  hide() { // 控制loading隐藏的方法
    options.showToast = false;
  }
}
export default {
  install(app) {
    // 3.0的全局挂载
    app.config.globalProperties.$loading = loadPlguin
  }
}

3.在main.js 中引入

import load from './utils/load'
import { createPinia } from 'pinia'
// 使用pinia
let app=createApp(App);
app.use(store).use(load).use(router).use(createPinia()).mount('#app')

4. 在组件中的调用

这里提一嘴,通常是用ref包装基本类型的数据,reactive包装引用类型的数据,ref的底层也是reactive

<template>
  <section>
    <img class="img" :src="url" alt="">
     <div v-for='(item,index) in list' :key='index'>{{item.email}}</div>
    <button @click='getData'>获取数据</button>
  </section>
</template>
<script setup>
import { getCurrentInstance, reactive, ref,toRefs } from 'vue';
const { proxy } = getCurrentInstance();//获取全局
const url = ref(null)
const state = reactive({
  list:[]
});
const getData = () => {
  proxy.$loading.show({ txt: 'motherFucker...' })
  fetch('https://jsonplaceholder.typicode.com/comments')
    .then(res => res.json())
    .then((res) => {
      proxy.$loading.hide();
      // url.value=res.src
      state.list = res;
    }).catch(err => {
      proxy.$loading.hide();
    })
}

let {list}=toRefs(state)//所有的state中都在此解构,模板中就省去用state.xx去取值了,直接取xx
</script>
<style scoped>
.img {
  width: 100%;
}
</style>

全局dialog封装 这里用的createVNode,render

image.png

1. 搞个dialog.vue组件

<template>
  <div id="confirm">
    <div class="contents">
      <div class="content-top">{{title}}</div>
      <div class="content-center">{{msg}}</div>
      <div class="content-bottom">
        <button type='primary' @click='okButton' class="left">{{okButtonTxt}}</button>
        <button type='info' @click='noButton' class="right">{{noButtonTxt}}</button>
      </div>
    </div>
  </div>
</template>
<script setup>
import { defineProps} from 'vue'
defineProps({
  title: {
    type: String,
    default: '这个是头部'
  },
  msg: {
    type: String,
    default: '这个是内容'
  },
  okButtonTxt: {
    type: String,
    default: '确定'
  },
  noButtonTxt: {
    type: String,
    default: '取消'
  },
  okButton: {
    type: Function
  },
  noButton: {
    type: Function
  }
})

</script>

<style scoped>
#confirm {
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.3);
}
.contents {
  width: 250px;
  height: 180px;
  border: 1px solid #ccc;
  border-radius: 10px;
  background-color: #fff;
  position: fixed;
  top: 50%;
  left: 50%;
  margin-top: -90px;
  margin-left: -125px;
}
.content-top {
  width: 100%;
  height: 40px;
  border-bottom: 1px solid #ccc;
  text-align: center;
  font-size: 20px;
  font-weight: 700;
  line-height: 40px;
}
.content-center {
  width: 90%;
  height: 80px;
  margin: 5px auto;
}
.content-bottom {
  width: 85%;
  height: 40px;
  margin: 0 auto;
  /* border:1px solid red; */
  position: relative;
}
.left {
  position: absolute;
  left: 0;
  width: 40%;
}
.right {
  position: absolute;
  right: 0;
  width: 40%;
}
</style>

2.utils中建立dialog.js

import {createVNode,render} from 'vue'
import Dialog from '@/components/dialog.vue'
const divDom = document.createElement('div')
divDom.setAttribute('class', 'dialog-container')
document.body.appendChild(divDom);

const dialogPlguin=(option)=>{
  return new Promise((resolve,reject)=>{
    const okButton=()=>{//确认
      render(null,divDom)
      resolve() 
    }
    const noButton=()=>{//  取消
      render(null,divDom)
      reject(new Error('取消'))
    }
    const vNode=createVNode(Dialog,{...option,okButton,noButton})
    render(vNode,divDom)
  })
}

export default {
  install(app) {
    app.config.globalProperties.$dialog = dialogPlguin
  }
}

3.main.js中引入

import load from './utils/load'
import dialog from './utils/dialog'
// 使用pinia
import { createPinia } from 'pinia'
// app.use(createPinia())// 
let app=createApp(App);
app.use(store).use(load).use(dialog).use(router).use(createPinia()).mount('#app')

4.组件中使用

const { proxy } = getCurrentInstance();
const getDialog = () => {
  proxy.$dialog({ title: '你好啊', noButtonTxt: '是' }).then(() => {
    console.log('成功')
  }).catch(err => {
    console.log(err)
  })
}

关于vue3.0动态节点的获取

<template>
  <div v-for='(item,index) in list' :key='index' :ref='listDom' :id='item.name'>{{item.name}}---{{item.id}}</div>
  <button @click="add"> 添加</button>
  <div>
     <button @click='getDom'>获取Dom</button>
  </div>
  <div>
    <button @click='addColor'>操作dom</button>
  </div>
<script setup>
import Child from '../components/HelloWorld.vue'
import { reactive, ref, onBeforeUpdate, toRef, toRefs } from 'vue'
const state=reactive({
  list:[]
})
const add=()=>{//添加动态的数据
    fetch('https://jsonplaceholder.typicode.com/comments/1')
    .then(res => res.json())
    .then((res) => {
      state.list.push(res)
    }).catch(err => {
    })
}
const addColor = () => {//操作获取的dom
  console.log(resultList)
  resultList.forEach((element, index) => {
    resultList[0].style = 'color:#e4393c';
  });
}
let resultList = [];//用来存储遍历的DOM元素
const listDom = (e) => {//这里是获取所有的dom节点并且存到resultList
  resultList.push(e);
};
onBeforeUpdate(() => {//更新之前一定重置,因为会拿着之前的数组进行拼接
  resultList = [];
});
let {list}=toRefs(state)
</script>

效果如下

image.png