vue3.0的弹窗
2020-12-01 17:55 muamaker 阅读(2578) 评论(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>