本文最后更新于 2 年前,文中所描述的信息可能已发生改变。
概述
阅读element notify组件(摸鱼真爽)。
createVNode/render 相比于模板语法有更高的自由度,在需要js的完全编程能力时非常有用。
编程式触发创建
- 定义组件模板(component)
- 定义props参数和触发事件
- 缓存vnode,后续通过vnode缓存计算定位高度
createVNode/h函数
传入的props对象的响应式,需要手动修改vnode的component的props属性
编程式触发创建
组件关闭和销毁
组件模板
- 定义组件 显示/关闭 过渡,
<Transition/>
包裹,动画离开后触发销毁事件 - 根据props动态计算style
- 样式设置:设置定位位移过渡
vue
<template>
<Transition @after-leave="$emit('destroy')">
<div v-show="visible" class="box" :id="id" :style="style"
>{{ message }}
<div @click="close">关闭</div>
</div>
</Transition>
</template>
<script lang="ts">
import { computed, ComputedRef, defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
props: {
message: {
type: String
},
id: {
type: String
},
offset: {
type: Number
}
},
emits: ['close', 'destroy'],
setup(props, { emit }) {
const style = computed(() => ({ top: props.offset + 'px' }))
const close = () => {
visible.value = false
emit('close', props.id)
}
const visible = ref(false)
onMounted(() => {
visible.value = true
})
return { style, close, visible }
}
})
</script>
<style scoped lang="scss">
.box {
width: 200px;
height: 100px;
background: #000;
color: white;
position: absolute;
right: 0;
transition: all 0.2s;
}
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
创建hook
typescript
import { VNode, createVNode, render } from 'vue'
import MessageBox from '../messageBox.vue'
const GAP_SIZE = 16
let seed = 1
const vnodeCache: { id: string; vnode: VNode }[] = []
export const notify = (options = {}) => {
const appendTo = document.body
const container = document.createElement('div')
const id = `messageBoxId--${seed++}`
// 缓存开罩属性
let cacheHeight = ''
let letcheIDX = 0
const vnode = createVNode(MessageBox, {
...options,
offset: vnodeCache.reduce((pre, curr) => pre + curr.vnode?.el?.offsetHeight + GAP_SIZE, options?.offset || 0),
id,
// 关闭时 删除缓存内dom信息,同时保存当前快照
onClose: (id: string) => {
letcheIDX = vnodeCache.findIndex(item => item.id === id)
cacheHeight = vnodeCache[letcheIDX]?.vnode?.el?.offsetHeight
vnodeCache.splice(letcheIDX, 1)
},
onDestroy: () => {
// 销毁dom
render(null, container)
const len = vnodeCache.length
// 设置位移 组件内部设置 定位动画过渡
for (let i = letcheIDX; i < len; i++) {
const { component } = vnodeCache[i]!.vnode
const oldSet = component!.props.offset
component!.props.offset = (oldSet as any).offset - Number(cacheHeight) - GAP_SIZE
}
}
})
// 缓存dom
vnodeCache.push({ id, vnode })
render(vnode, container)
// 插入dom
appendTo.appendChild(container.firstElementChild!)
}