Open HelloChunWei opened 2 years ago
在第二篇中我們可以動態決定呼叫哪個 modal 以及關閉 modal,(前情提要)但現在還是遇到了兩個問題:
那麼我們今天就來把這兩個問題給解決吧:
我們可以利用 v-bind 的方式將 props 傳下去。那我們預計在 index.vue 中是這樣的:
<template> <!-- 這裡也用 v-if 決定是否顯示,然後利用 is 決定哪個 modal component --> <!-- 將closeModal 在傳入 component --> <component v-if="isShow" :is="componentName" v-bind="myProps" @closeModal="closeModal" /> </template> <script lang="ts"> import { defineComponent, defineAsyncComponent, toRefs } from 'vue' export default defineComponent({ name: 'ModalIndex', props: ['isShow', 'component', 'closeModal', 'myProps'], setup(props) { const { component } = toRefs(props) const componentName = defineAsyncComponent(() => import(`./${component.value}.vue`)) return { componentName } } }) </script>
會在 index.vue 中多了一個 myProps 的參數,這代表這各個 modal 中自己定義的 props,然後利用 v-bind的方式傳下去。接下來修改一下 modal/index.ts:
import { createApp } from 'vue' // 改用 index.vue import Index from './index.vue' type componentNameType = 'testModal' export const useModal = () => { // 在呼叫openModal 的時候順便指定是要用哪一個 modal // component 利用 type 去指定,那目前只有 testModal 所以就先指定為 testModal const openModal = ({ component, myProps }: { component: componentNameType, myProps: any }) => { const container = document.createElement('div') // 直接remove const closeModal = () => { document.body.removeChild(container) } // 將 prop 傳入 const vnode = createApp(Index, { isShow: true, component, closeModal, myProps // 將 closeModal 傳入 }) document.body.appendChild(container) vnode.mount(container) } return { openModal } }
然後我們使用 openModal 的時候就是這樣:
openModal({ component: 'testModal', myProps: { user_id: 1 } })
那麼這樣我們就可以直接把 props 傳下去囉。所以這樣我們第一個問題就解決囉
目前的 close function 是利用 props 的方式傳下去.但這樣的寫法是非常不好的,雖然目前是在 template.vue 中利用 slot 的方式將 close 傳遞出來,但萬一有其他 component 要使用的話還是得用 props的方式傳遞。所以我希望這邊可以把他抽離出來。在這我使用 provide/inject 去解決,我們 provide close function,然後在 compoment 去inject,這樣就不用用 props 的方式去傳遞了。
但是: 理想很豐滿,現實很骨感
provide/inject 只限於在 setup 中使用,目前我們的 index.ts 是利用
const vnode = createApp(Index, { isShow: true, component, closeModal, myProps })
這種方式去建立 instance ,而這種方式我查了一下是無法使用 provide/inject 的,所以我們想要使用的話,index.ts 必須重構一番。
當初在這裡我想了大概兩天,都沒有想到一個方法去優化它,最後我在翻 element-ui 中的原始碼找到了靈感,他的 loading component 恰恰好解決我的問題: 原始碼
簡單來說就是在 mock 起一個 component 並利用 render function 的方式去掛載我們真正想要的 component,而 mock 的元件中就可以使用 setup 的 function, 既然知道原理了,我們就來修改一下吧:
export const useModal = () => { const openModal = ({ component, myProps }: { component: componentNameType, myProps: any }) => { const container = document.createElement('div') const _closeModal = function () { data.isShow = false container.parentNode!.removeChild(container) } const data = reactive({ isShow: true, component, myProps, close: _closeModal }) const mockModal = { name: 'mockModal', setup () { return { ...toRefs(data) } }, render () { return h(Index, { isShow: data.isShow, component: data.component, myProps: data.myProps, }) } } const vnode = createApp(mockModal) document.body.appendChild(container) vnode.mount(container) } return { openModal } }
將所有的 data 用 reactive 包起來,並且宣告一個 mockModal,然後利用 render function 把真正要的 component 渲染,至於為什麼data 為何要用 reactive 包起來?這樣我們在 closeModal 的時候,就可以直接 data.isShow = false,讓 modal 關閉。這樣就可以將動畫效果套入到我們的 modal 中。
那麼接下來我們就可以用 provide/inject 將我們的 close function 傳遞下去囉。
從官方的文件中我們可以知道:可以利用 InjectionKey 去限制他的型態,所以我們先新增一個文件叫做 provideInject.ts 文件:
import { InjectionKey, inject } from 'vue'; // 利用 Symbol 把參數包起來 export const CLOSE_MODAL: InjectionKey<() => void> = Symbol('closeModal') // 新增一個 injectStrict,會需要這個 function 的原因在於,inject時,必須還要給一個 default 值,不然 typescript 會認為他有可能是 undefined export const injectStrict = <T>(key: InjectionKey<T>, fallback?: T) => { const resolved = inject(key, fallback) if (!resolved) { throw new Error(`Could not resolve ${key}`) } return resolved; }
接著我們修改 index.ts:
export const useModal = () => { const openModal = ({ component, myProps }: { component: componentNameType, myProps: any }) => { const container = document.createElement('div') const _closeModal = function () { data.isShow = false container.parentNode!.removeChild(container) } const data = reactive({ isShow: true, component, myProps, close: _closeModal }) const mockModal = { name: 'mockModal', setup () { // 將我們的 close function provide 進去 provide(CLOSE_MODAL, data.close) return { ...toRefs(data) } }, render () { return h(Index, { isShow: data.isShow, component: data.component, myProps: data.myProps, }) } } const vnode = createApp(mockModal) document.body.appendChild(container) vnode.mount(container) } return { openModal } }
這樣之後我們就可以直接在 component 中直接 inject 囉,修改一下 template.vue:
<template> <div class="modal-mask"> <div ref="modalMask" class="modal-wrapper"> <div class="modal-container"> <div class="modal-header"> <slot name="header"> </slot> </div> <div class="modal-body" style="padding: 24px"> <slot name="body"> </slot> </div> <div class="modal-footer"> <slot name="footer" :close="close"> <button @click="close"> 取消 </button> </slot> </div> </div> </div> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue' import { CLOSE_MODAL, injectStrict } from './provideInject' export default defineComponent({ setup() { const modalMask = ref<HTMLElement | null>(null) // inject const close = injectStrict(CLOSE_MODAL) return { close, modalMask } }, }) </script>
如果想要在其他 component 用的話,只要在該 component inject 就好囉,這邊就不在把code 寫出來了。
到目前為止解決了我們第二篇所提到的問題,但在這架構中卻又發現了另一個問題:
我們雖然可以傳 props 下去到 component,但在使用的時候卻不知道該 component 到底吃哪些props,每次都要到那個component的檔案中去看他有什麼props,這樣來來回回後我自己覺得很惱人。 那這個問題,我們就等到下一篇在解決囉。
前言
在第二篇中我們可以動態決定呼叫哪個 modal 以及關閉 modal,(前情提要)但現在還是遇到了兩個問題:
那麼我們今天就來把這兩個問題給解決吧:
傳遞 props
我們可以利用 v-bind 的方式將 props 傳下去。那我們預計在 index.vue 中是這樣的:
會在 index.vue 中多了一個 myProps 的參數,這代表這各個 modal 中自己定義的 props,然後利用 v-bind的方式傳下去。接下來修改一下 modal/index.ts:
然後我們使用 openModal 的時候就是這樣:
那麼這樣我們就可以直接把 props 傳下去囉。所以這樣我們第一個問題就解決囉
重構close function
目前的 close function 是利用 props 的方式傳下去.但這樣的寫法是非常不好的,雖然目前是在 template.vue 中利用 slot 的方式將 close 傳遞出來,但萬一有其他 component 要使用的話還是得用 props的方式傳遞。所以我希望這邊可以把他抽離出來。在這我使用 provide/inject 去解決,我們 provide close function,然後在 compoment 去inject,這樣就不用用 props 的方式去傳遞了。
但是: 理想很豐滿,現實很骨感
provide/inject 只限於在 setup 中使用,目前我們的 index.ts 是利用
這種方式去建立 instance ,而這種方式我查了一下是無法使用 provide/inject 的,所以我們想要使用的話,index.ts 必須重構一番。
當初在這裡我想了大概兩天,都沒有想到一個方法去優化它,最後我在翻 element-ui 中的原始碼找到了靈感,他的 loading component 恰恰好解決我的問題: 原始碼
簡單來說就是在 mock 起一個 component 並利用 render function 的方式去掛載我們真正想要的 component,而 mock 的元件中就可以使用 setup 的 function, 既然知道原理了,我們就來修改一下吧:
將所有的 data 用 reactive 包起來,並且宣告一個 mockModal,然後利用 render function 把真正要的 component 渲染,至於為什麼data 為何要用 reactive 包起來?這樣我們在 closeModal 的時候,就可以直接 data.isShow = false,讓 modal 關閉。這樣就可以將動畫效果套入到我們的 modal 中。
那麼接下來我們就可以用 provide/inject 將我們的 close function 傳遞下去囉。
從官方的文件中我們可以知道:可以利用 InjectionKey 去限制他的型態,所以我們先新增一個文件叫做 provideInject.ts 文件:
接著我們修改 index.ts:
這樣之後我們就可以直接在 component 中直接 inject 囉,修改一下 template.vue:
如果想要在其他 component 用的話,只要在該 component inject 就好囉,這邊就不在把code 寫出來了。
結論
到目前為止解決了我們第二篇所提到的問題,但在這架構中卻又發現了另一個問題:
我們雖然可以傳 props 下去到 component,但在使用的時候卻不知道該 component 到底吃哪些props,每次都要到那個component的檔案中去看他有什麼props,這樣來來回回後我自己覺得很惱人。 那這個問題,我們就等到下一篇在解決囉。