代码改变世界

vue3.0的弹窗

2020-12-01 17:55  muamaker  阅读(2508)  评论(3编辑  收藏  举报

关于vue3.0写一个弹窗

 

一、官方提供的方法 teleport

<template>
    <teleport to="#modal-container">
          <div class="test">
            <el-button type="primary">这是一个测试</el-button>
          </div>
    </teleport>
</template>
<script>

export default {
    name:"Test"
}
</script>

  

to 指向的是一个 dom元素 id为 modal-container

 
缺点,只能引入后使用,不能通过js直接调用。
 
于是: 很自然想到 vue2.0 的 vue.extend 方法。 很可惜,没有。。。只能通过  createApp 自己再创建一个上下文、但是问题来了,上下文是不共享的。会出现element-plus组件无法正常显示
 
二、自定义弹出
 
import { createVNode ,render} from 'vue'
const body = document.body;
const root = document.createElement("div");
body.appendChild(root);
root.className = "custom-root";
export default {
    install(app){
        let div = document.createElement("div");
        root.appendChild(div);
        // com 为自己写的组件,  SoltChild 可以是自己的子组件 ,也可以不传
        let vm = createVNode(com,{},{
          
        });
        vm.appContext = app._context; // 这句很关键,关联起了数据
        render(vm,div);
        
    }
}

  

其中 vm.appContext = app._context;  非常关键 ,共享上下文
 
 
另外采用 jsx 语法:
Diag.vue
<script>
import {markRaw} from "vue"
export default {
    data(){
        return {
            visible:false,
            com:""
        }
    },
    methods:{
        open(com){
            this.com = markRaw(com);
            this.visible = true;
        }
    },
    render(){
        const com = this.com;
        return (
             <el-dialog
                title="提示"
                v-model={this.visible}
                width="30%"
            >
                <span>这是一段信息6666666</span>
                 <el-button type="primary" >el-button</el-button>
                 {
                    com && <com />
                 }
            </el-dialog>
        )
    }
}
</script>

  

index.js

import { createVNode ,render} from'vue'
const body = document.body;
const root = document.createElement("div");
body.appendChild(root);
root.className ="custom-root";

import Diag from "./Diag.vue";

let app;
export default {
    install(a){
        app = a;
    }
}


export const Create = (com)=>{
    let div = document.createElement("div");

    root.appendChild(div);
    // com 为自己写的组件,  SoltChild 可以是自己的子组件 ,也可以不传
    let vm = createVNode(Diag,{ref:"diag"},{
        // slots
        // default:()=>createVNode(SoltChild)
    });
    vm.appContext = app._context;// 这句很关键,关联起了数据
    render(vm,div);

    vm.component.proxy.open && vm.component.proxy.open(com);
    console.log(vm);
}

  

 jsx 的中级 ts 版本
Diaglog.tsx
import { markRaw ,defineComponent,reactive, toRefs} from 'vue'

interface IAny{
    [x:string]:any
}

export interface IProps  {
    diagProps?: IAny;
    props?:IAny;
    ok?:Function;
    cancel?:Function;
}

export interface IParams extends IProps{
    children?: any;
    footer?: any;
    header?:any;
    onClose?: Function;
}


interface IState{
    params: IParams | null;
    visible:boolean;
}

export default defineComponent({
    setup(){
        const state = reactive<IState>({
            params:null ,
            visible: false ,
        });

        const open = (com:IParams)=>{
            state.params = markRaw<IParams>(com);
            state.visible =true;
        }
        const ok = (...a:Array<any>)=>{
            state.params?.ok  && state.params?.ok(...a);
            state.params?.onClose && state.params?.onClose(...a);
            state.visible =false;
        }
        const cancel = (...a:Array<any>)=>{
            state.params?.cancel  && state.params?.cancel(...a);
            state.params?.onClose && state.params?.onClose(...a);
            state.visible =false;
        }


        const beforeClose = (done:Function)=>{
            if(state.params?.diagProps && state.params?.diagProps["before-close"]){
                new Promise((resolve)=>{
                    return state.params?.diagProps && state.params?.diagProps["before-close"](resolve);
                }).then((res : any)=>{
                    ok(res);
                    done();
                });
            }else{
                ok();
                done && done();
            }
        }
        return {
            ...toRefs(state),
            open,
            ok,
            cancel,
            beforeClose
        };
    },
    render(ctx:any){
        if(!ctx.params || !ctx.params.children || !ctx.visible){
            return <div></div>;
        }
        const com =ctx.params.children;
        const Header = ctx.params.header;
        const Footer   =  ctx.params.footer;

        const modal = {
            ok:ctx.ok,
            cancel:ctx.cancel
        }
       console.log(JSON.stringify(ctx.params.diagProps))
        return (
            <el-dialog
                title="弹窗"
                {...ctx.params.diagProps}
                before-close={ctx.beforeClose}
                v-model={ctx.visible}
                v-slots={
                    {
                        footer: ()=>Footer ? <Footer modal={modal} /> : null,
                        header:()=>Header ? <Header modal={modal}  /> : null
                    }
                }
            >
                 {
                    com && <com  modal={modal} {...ctx.params.props} />
                 }
            </el-dialog>
        )
    }
});

  

  

index.ts

import { createVNode ,render}from'vue'
import type {VNode,App} from "vue"

const body = document.body;
const root = document.createElement("div");
body.appendChild(root);
root.className ="custom-root";
 
import Diag from "./Diaglog";

import type {IParams} from "./Diaglog"
export type {IProps} from "./Diaglog"
 
let app:App|null = null;


export default {
    install(a:App){
        app = a;
    }
}

let list : Array<VNode> = [];



const getIstance = (params:IParams)=>{
    let instance : VNode ;
    
    if(list.length > 0){
        instance =  list.shift() as VNode;
    }else{
        let div = document.createElement("div");
 
        root.appendChild(div);
        // com 为自己写的组件,  SoltChild 可以是自己的子组件 ,也可以不传
        let vm = createVNode(Diag,{
            // ref:xx
              // 做用域插槽的格式为
            // { name: props => VNode | Array<VNode> }
            // scopedSlots: {
            //     default: props => createElement('span', props.text)
            // },
            // // 若是组件是其它组件的子组件,需为插槽指定名称
            // slot: 'name-of-slot',
        },{
            
        });
        vm.appContext = app!._context;// 这句很关键,关联起了数据
        render(vm,div);
        instance = vm;
    }

    const vvm = (instance.component as any)
    new Promise((resolve)=>{
        vvm.proxy.open &&   vvm.proxy.open({...params,onclose:resolve});
    }).then(()=>{
        // 关闭了弹窗,就回收
        list.push(instance);
    })
    

    return {
        destroy(){
            vvm.proxy.destroy &&   vvm.proxy.destroy();
        },
        ok(...a:Array<any>){
            vvm.proxy.ok &&   vvm.proxy.ok(...a);
        },
        cancel(...a:Array<any>){
            vvm.proxy.cancel &&   vvm.proxy.cancel(...a);
        }
    }
}
 
export const Create = (params:IParams)=>{
    return getIstance(params);
}


export const CreateAsync = (params:IParams)=>{
    return new Promise((resolve,reject)=>{
         let obj:IParams = {
             ...params,
             ok(...a:Array<any>){
                resolve(a[0]);
                params.ok  && params.ok(...a)
             },
             cancel(...a:Array<any>){
                reject(a[0]);
                params.cancel  && params.cancel(...a)
             }
            }
            getIstance(obj);
    });
}

  

  

参考:element plus 的 message-box 实现方式

一样的会有 

vnode.appContext = appContext
 
此处 appContext 有两个来源
1、与上面的弹窗实现一样, app._context

2、在组件的 setup 里面获取

<script lang="ts">
import { defineComponent,getCurrentInstance } from 'vue'
export default defineComponent({
  setup() {
      const { appContext } = getCurrentInstance()!;
      
  },
})
</script>