Nuxt3 中如何注册带有document的自定义Vue插件(解决document is not defined问题)

1.场景

想注册一个全局的loading插件,通过方法提供显示和隐藏,如下所示:

<template>
    <div v-show="isShow" class="loading">
    
      <div class="loading-content">Loading...</div>

    </div>
  
</template>

<script setup lang="ts">
import { ref } from "vue";
const isShow = ref(false); //定位loading 的开关
const show = () => {
  isShow.value = true;
};
const hide = function ()  {
  isShow.value = false;
};
//对外暴露 当前组件的属性和方法
defineExpose({
  isShow,
  show,
  hide,
});
</script>

<style scoped lang="scss">
.loading {
  position: fixed;
  z-index: 999;
  inset: 0;
  // background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  &-content {
    font-size: 40px;
    color: #a9a9a9;
  }
}
</style>

想法是在插件里注册,也就是先渲染并插入document.body中,把暴露的方法作为应用实例的全局属性。这样在其他组件使用时,就不需要引入该组件,只需要调用全局里对应的方法即可使用。

2.注册

其实官方文档有注册方法,但是说明不清晰,没有深入测试成功。之后又照着网上其他的写法(大都是Nuxt2),都不成功,报错document is not defined。

// plugins/vue-gtag.client.js
// 官方写法,详见https://nuxt.com.cn/docs/guide/directory-structure/plugins

import VueGtag from 'vue-gtag-next'
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(VueGtag, {
    property: {
      id: 'GA_MEASUREMENT_ID'
    }
  })
})

最后还是得依靠官方文档,多思考了一阵后就写出解决方案了,如下:

import { createVNode, render} from "vue";
import type { VNode, App } from "vue";
import Loading from "@/components/Loading.vue";

type Load = {
  show: () => {},
  hide: () => {}
}

// 声明注入属性的类型
declare module "@vue/runtime-core" {  
  interface ComponentCustomProperties {
    $loading: Load; 
  }
}

declare module '#app' {
  interface NuxtApp {
    $loading: Load;
  }
}

/* 
  Nuxt3 注册带有浏览器元素(如document或window等)的插件所需要的条件:
  1. 要在vue app实例中注册,如下所示,通过Nuxt.vueApp.use注册该插件
  2. 插件文件名要携带client后缀,表示这是个只在客户端执行的插件
*/
export default defineNuxtPlugin((NuxtApp) => {
  NuxtApp.vueApp.use({
    async install(app: App){
      const vnode: VNode = createVNode(Loading);
      //render 把Vnode 生成真实DOM 并且挂载到指定节点
      const element = document.createElement('div')
      document.body.appendChild(element)
      render(vnode, element);
      // Vue 提供的全局配置 可以自定义
      app.config.globalProperties.$loading = {
        // isShow: vnode.component?.exposed?.isShow,
        show: () => vnode.component?.exposed?.show(),
        hide: () => vnode.component?.exposed?.hide(),
      };
    }
  })
})

不需要在nuxt.config.ts里设置plugins: [{ src: '...', ssr: false}],很方便。

3.运用

当前我在某一组件里想要使用,则如下所示:

// setup环境

import { getCurrentInstance } from 'vue'

// 通过如下方式获取$loading全局变量
const $loading = getCurrentInstance()!.appContext.config.globalProperties.$loading;

// 本来以为需要process.client的条件判断,实际运行时发现没有该条件也可以
$loading.show();  // 显示
$loading.hide();  // 隐藏

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值