這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、組件設計 組件就是把圖形、非圖形的各種邏輯均抽象為一個統一的概念(組件)來實現開發的模式 現在有一個場景,點擊新增與編輯都彈框出來進行填寫,功能上大同小異,可能只是標題內容或者是顯示的主體內容稍微不同 這時候就沒必要寫兩個組件,只需要 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一、組件設計
組件就是把圖形、非圖形的各種邏輯均抽象為一個統一的概念(組件)來實現開發的模式
現在有一個場景,點擊新增與編輯都彈框出來進行填寫,功能上大同小異,可能只是標題內容或者是顯示的主體內容稍微不同
這時候就沒必要寫兩個組件,只需要根據傳入的參數不同,組件顯示不同內容即可
這樣,下次開發相同界面程式時就可以寫更少的代碼,意義著更高的開發效率,更少的 Bug
和更少的程式體積
二、需求分析
實現一個Modal
組件,首先確定需要完成的內容:
-
遮罩層
-
標題內容
-
主體內容
-
確定和取消按鈕
主體內容需要靈活,所以可以是字元串,也可以是一段 html
代碼
特點是它們在當前vue
實例之外獨立存在,通常掛載於body
之上
除了通過引入import
的形式,我們還可通過API
的形式進行組件的調用
還可以包括配置全局樣式、國際化、與typeScript
結合
三、實現流程
首先看看大致流程:
-
目錄結構
-
組件內容
-
實現 API 形式
-
事件處理
-
其他完善
目錄結構
Modal
組件相關的目錄結構
├── plugins │ └── modal │ ├── Content.tsx // 維護 Modal 的內容,用於 h 函數和 jsx 語法 │ ├── Modal.vue // 基礎組件 │ ├── config.ts // 全局預設配置 │ ├── index.ts // 入口 │ ├── locale // 國際化相關 │ │ ├── index.ts │ │ └── lang │ │ ├── en-US.ts │ │ ├── zh-CN.ts │ │ └── zh-TW.ts │ └── modal.type.ts // ts類型聲明相關
因為 Modal 會被 app.use(Modal)
調用作為一個插件,所以都放在plugins
目錄下
組件內容
首先實現modal.vue
的主體顯示內容大致如下
<Teleport to="body" :disabled="!isTeleport"> <div v-if="modelValue" class="modal"> <div class="mask" :style="style" @click="maskClose && !loading && handleCancel()" ></div> <div class="modal__main"> <div class="modal__title line line--b"> <span>{{ title || t("r.title") }}</span> <span v-if="close" :title="t('r.close')" class="close" @click="!loading && handleCancel()" >✕</span > </div> <div class="modal__content"> <Content v-if="typeof content === 'function'" :render="content" /> <slot v-else> {{ content }} </slot> </div> <div class="modal__btns line line--t"> <button :disabled="loading" @click="handleConfirm"> <span class="loading" v-if="loading"> ❍ </span>{{ t("r.confirm") }} </button> <button @click="!loading && handleCancel()"> {{ t("r.cancel") }} </button> </div> </div> </div> </Teleport>
最外層上通過Vue3 Teleport
內置組件進行包裹,其相當於傳送門,將裡面的內容傳送至body
之上
並且從DOM
結構上來看,把modal
該有的內容(遮罩層、標題、內容、底部按鈕)都實現了
關於主體內容
<div class="modal__content"> <Content v-if="typeof content==='function'" :render="content" /> <slot v-else> {{content}} </slot> </div>
可以看到根據傳入content
的類型不同,對應顯示不同得到內容
最常見的則是通過調用字元串和預設插槽的形式
// 預設插槽 <Modal v-model="show" title="演示 slot"> <div>hello world~</div> </Modal> // 字元串 <Modal v-model="show" title="演示 content" content="hello world~" />
通過 API 形式調用Modal
組件的時候,content
可以使用下麵兩種
- h 函數
$modal.show({ title: '演示 h 函數', content(h) { return h( 'div', { style: 'color:red;', onClick: ($event: Event) => console.log('clicked', $event.target) }, 'hello world ~' ); } });
- JSX
$modal.show({ title: '演示 jsx 語法', content() { return ( <div onClick={($event: Event) => console.log('clicked', $event.target)} > hello world ~ </div> ); } });
實現 API 形式
那麼組件如何實現API
形式調用Modal
組件呢?
在Vue2
中,我們可以藉助Vue
實例以及Vue.extend
的方式獲得組件實例,然後掛載到body
上
import Modal from './Modal.vue'; const ComponentClass = Vue.extend(Modal); const instance = new ComponentClass({ el: document.createElement("div") }); document.body.appendChild(instance.$el);
雖然Vue3
移除了Vue.extend
方法,但可以通過createVNode
實現
import Modal from './Modal.vue'; const container = document.createElement('div'); const vnode = createVNode(Modal); render(vnode, container); const instance = vnode.component; document.body.appendChild(container);
在Vue2
中,可以通過this
的形式調用全局 API
export default { install(vue) { vue.prototype.$create = create } }
而在 Vue3 的 setup
中已經沒有 this
概念了,需要調用app.config.globalProperties
掛載到全局
export default { install(app) { app.config.globalProperties.$create = create } }
事件處理
下麵再看看看Modal
組件內部是如何處理「確定」「取消」事件的,既然是Vue3
,當然採用Compositon API
形式
// Modal.vue setup(props, ctx) { let instance = getCurrentInstance(); // 獲得當前組件實例 onBeforeMount(() => { instance._hub = { 'on-cancel': () => {}, 'on-confirm': () => {} }; }); const handleConfirm = () => { ctx.emit('on-confirm'); instance._hub['on-confirm'](); }; const handleCancel = () => { ctx.emit('on-cancel'); ctx.emit('update:modelValue', false); instance._hub['on-cancel'](); }; return { handleConfirm, handleCancel }; }
在上面代碼中,可以看得到除了使用傳統emit
的形式使父組件監聽,還可通過_hub
屬性中添加 on-cancel
,on-confirm
方法實現在API
中進行監聽
app.config.globalProperties.$modal = { show({}) { /* 監聽 確定、取消 事件 */ } }
下麵再來目睹下_hub
是如何實現
// index.ts app.config.globalProperties.$modal = { show({ /* 其他選項 */ onConfirm, onCancel }) { /* ... */ const { props, _hub } = instance; const _closeModal = () => { props.modelValue = false; container.parentNode!.removeChild(container); }; // 往 _hub 新增事件的具體實現 Object.assign(_hub, { async 'on-confirm'() { if (onConfirm) { const fn = onConfirm(); // 當方法返回為 Promise if (fn && fn.then) { try { props.loading = true; await fn; props.loading = false; _closeModal(); } catch (err) { // 發生錯誤時,不關閉彈框 console.error(err); props.loading = false; } } else { _closeModal(); } } else { _closeModal(); } }, 'on-cancel'() { onCancel && onCancel(); _closeModal(); } }); } };
其他完善
關於組件實現國際化、與typsScript
結合,大家可以根據自身情況在此基礎上進行更改
參考文獻
- https://segmentfault.com/a/1190000038928664