这类组件的特点是在当前Vue实例之外存在,通常挂载与body
,上在使用element-ui
弹窗组件的时候,我们可以看到这一点:
并且它们是通过JS动态创建的,不需要在任何组件中声明。接下来我们看具体代码实现。
创建一个js
文件,用来创建弹窗实例
import Vue from 'vue'
export default function create (comp, props) {
const app = new Vue({
render (h) {
return h(comp, { props })
}
}).$mount()
// app.$el为该虚拟DOM的真实DOM
document.body.appendChild(app.$el)
// 销毁
const com = app.$children[0]
com.remove = function () {
document.body.removeChild(app.$el)
app.$destroy()
}
return com
}
默认导出一个创建弹窗组件实例的方法,入参1为需要展示的弹窗组件,入参2为该组件的配置对象。首先通过render
函数将组件转换成vNode,由于不能通过$mount
将组件直接挂载到body
上,所以需要我们手动挂载,document.body.appendChild(app.$el)
,并且为该组件实例添加移除销毁的方法,避免内存泄漏。
模板转换成浏览器认识的
HTML
过程,
1、template->AST render(compile解析template)
2、AST render->vNode (render函数方法运行)
3、VNode->DOM (vDom.patch)
需要展示的弹窗组件
<template>
<div class="notice" v-if="isShow">
<h3>{{ title }}</h3>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
props: {
title: { type: String },
message: { type: String },
duration: { type: Number, default: 2000 }
},
data () {
return {
isShow: false
}
},
methods: {
show () {
this.isShow = true
setTimeout(() => {
this.hide()
}, this.duration)
},
hide () {
this.isShow = false
this.remove()
}
}
}
</script>
<style lang="scss">
.notice {
position: absolute;
top: 20px;
right: 0;
left: 0;
margin: auto;
width: 300px;
height: 300px;
background: #ff00ff;
}
</style>
使用弹窗组件
<template>
<div id="app">
<button @click="clickHandle">click</button>
</template>
<script>
import create from './utils/create'
import KNotice from './components/notice/KNotice'
export default {
name: 'App',
methods: {
clickHandle () {
const notice = create(KNotice, {
title: 'title',
message: 'this is mesage',
duration: 1000
})
notice.show()
},
}
}
</script>
<style lang="scss"></style>
树形组件是典型的递归组件,像element-ui
中的菜单组件都是这一类组件的实现
<template>
<div>
<li>
<p @click="toggle">
{{ model.title }}
<span v-if="isFolder">[{{ open ? "-" : "+" }}]</span>
</p>
<ul v-show="open" v-if="isFolder">
<item v-for="(item,index) in model.children" :key="index" :model="item"> </item>
</ul>
</li>
</div>
</template>
<script>
export default {
name: 'item',
props: {
model: {
type: Object
}
},
data () {
return {
open: false
}
},
computed: {
isFolder () {
return this.model.children && this.model.children.length
}
},
methods: {
toggle () {
if (this.isFolder) {
this.open = !this.open
}
}
}
}
</script>
<style lang="scss" scoped></style>
<template>
<div id="app">
<ul>
<item :model="model"></item>
</ul>
</div>
</template>
<script>
import Item from './components/tree'
export default {
name: 'App',
components: {
Item
},
data () {
return {
list: {
id: 1,
title: '递归组件1',
children: [
{
id: 2,
title: '递归组件2',
children: [
{
id: 4,
title: '递归组件4'
},
{
id: 5,
title: '递归组件5'
}
]
},
{
id: 3,
title: '递归组件3'
}
]
},
model: {
title: 'a',
children: [
{
title: 'b'
},
{
title: 'c',
children: [
{
title: 'cc'
},
{
title: 'dd'
}
]
}
]
}
}
},
}
</script>
<style lang="scss"></style>