my-blog:https://tuimao233.gitee.io/ma...css
經過前篇文章的介紹,你們已經瞭解了虛擬節點和瞬移組件,接下來咱們利用虛擬節點與瞬移組件,封裝一個模態框組件。node
首先,得先明確咱們目標,就是咱們想要作出來的效果,就是兼容兩個方式調用的模態框組件git
第一種經過 template 直接使用:app
<model v-model="show" title="標題" @confirm="onConfirm" @clone="onClone"> 我是模態框文字 </model>
第二種是直接 JavaScript 調起:函數
Modal({title: '標題', content: '我是模態框文字'}) .then(()=> { }) .catch(()=> { })
從這兩段方式能夠看出,不管是經過 Modal
,仍是經過<model>..</model>
,可傳入參數都保持一致,因而可知,組件調用方式傳參一致,因此咱們首先新建components/Modal/props.ts
,在外部定義 props
參數類型:post
/** 模態框固定 props 參數, 用於調用模態框成功|關閉|銷燬 */ export const modalProps = { // 是否展現組件 modelValue: Boolean, // 組件消失時(移除實例) vanish: Function, // 組件調用成功事件 resolve: Function, // 組件調用失敗事件 reject: Function } /** 組件內傳入 props 參數, 用於模態框自定義功能 */ export const componentProps = { // 模態框標題 title: String, // 模態框內容 content: String } /** 組件內全部 Props 參數, 合併參數 */ export const props = {...modalProps, ...componentProps}
這一步完成以後,咱們在建立components/Modal/index.vue
,導入 props
類型:測試
<template> <div></div> </template> <script lang="ts"> import { defineComponent } from 'vue' import { props } from './props' export default defineComponent({ props }) </script> <style lang="scss" scoped></style>
到這一步後,咱們在定義一個經過js代碼渲染組件的方法:動畫
// components/Modal/utils.vue import { Component, h, render } from "vue" /** * 渲染組件實例 * @param Constructor 組件 * @param props 組件參數 * @returns 組件實例 */ export const renderInstance = (Constructor: Component, props: Record<string, any>) => { // 建立組件容器, 這一步是必須的, 在銷燬組件時會使用到 const container = document.createElement('div') // 在 props 添加組件消失鉤子, 移除當前實例, 將銷燬方法提供給組件 // 這裏不須要調用 document.body.removeChild(container.firstElementChild) // 由於調用 render(null, container) 爲咱們完成了這項工做 props.vanish = () => { render(null, container) } // 建立虛擬節點, 渲染組件 const vnode = h(Constructor, props) render(vnode, container) // 添加子元素(組件)至父元素 document.body.appendChild(container.firstElementChild) }
渲染方法定義完成後,咱們就能夠先把經過 js 調起的方法給作了:
import { ExtractPropTypes, ref } from "vue" import Index from './index.vue' import { componentProps } from './props' import { renderInstance } from "./utils" /** 組件 Props 類型, ExtractPropTypes 可將 Constructor 轉換爲對應值類型 */ type Props = ExtractPropTypes<typeof componentProps> /** 組件調用 resolve 返回結果 */ type Result = { path: string }[] /** * 模態框調用方法 * @param props * @returns {Promise} */ export const Modal = (props: Props) => { return new Promise<Result>((resolve, reject) => { renderInstance(Index, { // 這裏 modelValue, 爲了使組件可修改, 須要傳入 ref // 注意這塊地方,咱們將這個值設置爲 true 爲了調起即直接展現組件 modelValue: ref(true), ...props, resolve, reject }) }) }
這裏須要注意的是,經過 h
函數建立的實例,其 props
在組件中,沒法經過 emit
修改,修改會失效,因此爲了解決這個問題,須要在調起方法傳入 modelValue Ref
。
接下來咱們進行完善components/Modal/index.vue
組件的模態框邏輯:
<template> <teleport to="body"> <!-- after-leave 組件動畫結束時, 調用銷燬組件(假若有的話) --> <transition name="fade" @after-leave="vanish"> <div class="base-model__mask" v-show="show"> <div class="base-model__content"> <div class="base-model__title">{{ title }}</div> <!-- 插入自定義插槽, 這裏判斷默認插槽有沒有使用 --> <!-- 若是使用, 則渲染插槽, 若是沒有, 則渲染 content --> <slot v-if="$slots['default']" /> <template v-else>{{ content }}</template> <div class="base-model__control"> <span @click="onConfirm">肯定</span> <span @click="onClone">關閉</span> </div> </div> </div> </transition> </teleport> </template> <script lang="ts"> import { defineComponent, computed, isRef, nextTick, watch } from 'vue' import { props } from './props' export default defineComponent({ props, setup: (props, { emit }) => { // 組件顯示的數據雙向代理 const modelValue = computed({ get: () => <boolean>props.modelValue, set: () => emit('update:modelValue') }) // Modal 方法調用傳入 props 沒法經過 emit 修改 // 因此假如傳入直接是一個 ref 則直接使用 const show = isRef(props.modelValue) ? props.modelValue : modelValue // 假如初始化爲 true , 切換狀態讓動畫正常顯示 if (show.value) { show.value = false nextTick(() => show.value = true) } // 關閉事件, 調用 reject, 爲了兼容模板上直接使用組件, 還要在調用一次 clone 事件 const onClone = () => { props.reject?.() emit('clone') show.value = false } // 肯定事件, 調用 resolve, 爲了兼容模板上直接使用組件, 還要在調用一次 confirm 事件 const onConfirm = () => { props.resolve?.() emit('confirm') show.value = false } return { show, onConfirm, onClone } } }) </script> <style lang="scss" scoped> .base-model__mask { position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.4); } .base-model__content { position: absolute; border-radius: 20px; width: 600px; height: 300px; background-color: #ffffff; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 20px; } .base-model__control { position: absolute; right: 0; bottom: 20px; span { margin-right: 20px; } } /* 組件動畫 start */ .fade-enter-active, .fade-leave-active { transition: opacity 0.2s; } .fade-enter-from, .fade-leave-to { opacity: 0; } .fade-enter-top, .fade-leave-from { opacity: 1; } /* 組件動畫 end */ </style>
到了這裏,咱們能夠測試一下組件調用是否正常,例如,咱們經過使用 template
組件方式調用:
<template> <img alt="Vue logo" src="./assets/logo.png" @click="show = true" /> <modal @clone="onClone" @confirm="onConfirm" v-model="show" title="我是標題" > 啦啦啦我是自定義內容 </modal> </template> <script lang="ts"> import { defineComponent, ref } from 'vue' import Modal from './components/Modal/index.vue'; export default defineComponent({ components: { Modal }, setup: () => { const show = ref(false) const onClone = () => { console.log('模態框點擊關閉') } const onConfirm = () => { console.log('模態框點擊確認') } return { onClone, onConfirm, show } } }) </script>
在測試一下,經過 JavaScript 調用模態框:
<template> <img alt="Vue logo" src="./assets/logo.png" @click="onClick" /> </template> <script lang="ts"> import { defineComponent, ref } from 'vue' import { Modal } from './components/Modal'; export default defineComponent({ components: { }, setup: () => { const onClick = () => { Modal({title: '我是標題~~~', content: '我是內容~~~'}) .then(() => { console.log('組件調用成功') }) .catch(() => { console.log('組件調用失敗') }) } return {onClick} } }) </script>
到這裏,整個模態框的基本邏輯都組成了,在這基礎下,就可基於需求下完善模態框與定製內容,也可經過該方法,二次封裝 el-dialog
組件,只須要將 components/Modal/index.vue
的邏輯修改一下便可,下一篇文章,咱們在這基礎下在進行完善,使得組件能徹底勝任業務需求。