【问题标题】:How do I create a global component without injection?如何在不注入的情况下创建全局组件?
【发布时间】:2020-12-05 05:47:53
【问题描述】:

有一种情况,我们需要一个全局组件来使用它,比如this.$toast('some words')this.$dialog({title:'title words',contentText:'some words')

在 Vue 2.x 中,我们可以将Toast.vue 的方法添加到Vue.prototype,并在任何地方调用Toast.vue 的方法。但是我们如何在 Vue 3.x 中做到这一点?

我在vue-next阅读了i18n插件演示的文档。但它需要将 i18n 插件注入到每个需要使用它的组件中。不方便。

【问题讨论】:

    标签: vue.js vuejs3


    【解决方案1】:

    一种在 vue3 应用中任意位置显示组件而不注入的方式

    机制

    每次都将组件挂载到 dom 中。

    实现

    以“吐司”为例:

    第 1 步:创建 SFC (Toast.vue)

    <template>
      <transition name="fade">
        <div class="toast" v-html="msg" :style="style" @click="closeHandle"></div>
      </transition>
    </template>
    
    <script>
      import {ref,computed,onMounted,onUnmounted} from 'vue'
      export default {
        name: "Toast",
        props:{
          msg:{type:String,required:true},
          backgroundColor:{type:String},
          color:{type:String},
          // closing the Toast when timed out. 0:not closed until to call this.$closeToast()
          timeout:{type:Number,default:2000, validate:function (val){return val >= 0}},
          // closing the Toast immediately by click it, not wait the timed out.
          clickToClose:{type:Boolean, default: true},
          // a function provied by ToastPlugin.js, to unmout the toast. 
          close:{type:Function,required: true}
        },
    
        setup(props){
          let innerTimeout = ref();
          const style = computed(
              ()=>{return{backgroundColor:props.backgroundColor ? props.backgroundColor : '#696969', color:props.color ? props.color : '#FFFFFF'}}
          );
    
          onMounted(()=>{
            toClearTimeout();
            if(props.timeout > 0)
              innerTimeout.value = setTimeout(()=>{ props.close(); },props.timeout);
          });
    
          /**
           * when toast be unmounted, clear the 'innerTimeout'
           */
          onUnmounted(()=>{toClearTimeout()})
    
          /**
           * unmount the toast
           */
          const closeHandle = () => {
            if(props.clickToClose)
              props.close();
          }
    
          /**
           * to clear the 'innerTimeout' if it exists.
           */
          const toClearTimeout = ()=>{
            if(innerTimeout.value){
              try{
                clearTimeout(innerTimeout.value);
              }catch (e){
                console.error(e);
              }
            }
          }
    
          return {style,closeHandle};
        },
      }
    </script>
    
    <style scoped>
      .toast{position: fixed; top: 50%; left: 50%; padding: .3rem .8rem .3rem .8rem; transform: translate(-50%,-50%); z-index: 99999;
        border-radius: 2px; text-align: center; font-size: .8rem; letter-spacing: .1rem;}
      .fade-enter-active{transition: opacity .1s;}
      .fade-leave-active {transition: opacity .3s;}
      .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {opacity: 0;}
    
    </style>
    

    第 2 步:创建插件 (ToastPlugin.js)

    import Toast from "./Toast.vue";
    import {createApp} from 'vue'
    const install = (app) => {
        // dom container for mount the Toast.vue
        let container;
        // like 'app' just for Toast.vue
        let toastApp;
        // 'props' that Toast.vue required.
        const baseProps = {
            // define a function to close(unmount) the toast used for 
            // case 1: in Toast.vue "click toast appeared and close it"
            // case 2: call 'this.$closeToast()' to close the toast in anywhere outside Toast.vue
            close:()=> {
                if (toastApp)
                    toastApp.unmount(container);
    
                container = document.querySelector('#ToastPlug');
                if(container)
                    document.body.removeChild(container);
            }
        };
        
        // show Toast
        const toast = (msg)=>{
            if(typeof msg === 'string')
                msg = {msg};
    
            const props = {...baseProps,...msg}
            console.log('props:',JSON.stringify(props));
    
            // assume the toast(previous) was not closed, and try to close it.
            props.close();
    
            // create a dom container and mount th Toast.vue
            container = document.createElement('div');
            container.setAttribute('id','ToastPlug');
            document.body.appendChild(container);
            toastApp = createApp(Toast, props);
            toastApp.mount(container);
        }
        
        // set 'toast()' and 'close()' globally
        app.config.globalProperties.$toast = toast;
        app.config.globalProperties.$closeToast = baseProps.close;
    }
    
    export default install;
    

    第 3 步:用法

    在 main.js 中

    import ToastPlugin from 'xxx/ToastPlugin'
    import { createApp } from 'vue'
    
    const app = createApp({})
    app.use(ToastPlugin)
    
    // then the toast can be used in anywhere like this:
    this.$toast('some words')
    this.$toast({msg:'some words',timeout:3000})
    

    【讨论】:

      【解决方案2】:

      Vue 3 提供了一个API 用于附加全局属性:

      import { createApp } from 'vue'
      
      const app = createApp({})
      app.config.globalProperties.$toast = () => { /*...*/ }
      

      【讨论】:

      • 谢谢!如果 toast 被写为包含名为“show(msg)”的方法的 SFC,有没有办法在 Toast.vue 之外调用它? PS:'show'方法不能包含'setTimeout'来关闭Toast
      • “在 Toast.vue 之外调用它”是什么意思?
      • 我想在app.config.globalProperties 中定义一个名为$toast 的函数来调用Toast.vue 中的show()。目的是在 vue 应用程序的任何地方显示 toast,而不需要导入或“注入”Toast.vue。经过研究,我找到了一种方法:app.config.globalProperties.$toast的行为是“每次调用$toast时创建一个新的应用程序实例并moute Toast.vue”。也许这不是最好的方法,但它确实有效。
      猜你喜欢
      • 1970-01-01
      • 2023-01-30
      • 1970-01-01
      • 2016-12-20
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多