vue3.0的弹窗
2020-12-01 17:55 muamaker 阅读(2679) 评论(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>
浙公网安备 33010602011771号