基于Vue3+ts封裝一個簡單版的Message組件
Vue3+ts封裝一個Message組件
項目中需要使用信息提示框的功能,ui組件庫使用的是字節(jié)的arco-design-vue。看了一下,現(xiàn)有的Message不滿足要是需求,直接使用message組件的話,改樣式太麻煩。Notification組件樣式倒是符合了,但是彈出的位置不符合,查看了一下相關(guān)api,這位置不支持"top"。既然如此,那就去查看它倆的源碼,找到我想要的,修修改改,自己也寫一個,嘻嘻。
源碼分析
源碼(以Message組件為例)主要分為三個模塊:message-item.vue、message-list.vue、message.ts。下面簡單介紹一下這三個模塊
- message-item.vue: 主要是編寫Message信息彈框的樣式和彈框按時(定時)自動消失(關(guān)閉)的邏輯
- message-list.vue:主要就是添加動畫
- message.ts:這個最主要的。這里面要實現(xiàn)創(chuàng)建message、刷新message、關(guān)閉message、銷毀message以及message分類等一系類邏輯,同時還要把這些邏輯封裝(暫時找不到更好的詞來表達)成一個對象拋出去。沒錯,這是一個單例設(shè)計模式(全局永遠只有一個Message對象)
代碼實現(xiàn)
在完全實現(xiàn)之前,我看 message-list.vue 組件中的邏輯那么簡單,想也不想就直接把它跟 message-item.vue 組成了一個組件 message-ul.vue,等到要實現(xiàn)邏輯的時候,才發(fā)現(xiàn)自己這個寫不行,因為每一個Message都可以設(shè)置自己的持續(xù)時長,時間一到就自己關(guān)閉了,不會影響到其他的。但是在 message-ul.vue 組件中,處理的是一個列表,要保證每個列表項的定時關(guān)閉不影響其他的,我沒想到解決辦法(嗚嗚嗚,我太笨了 ),所以最后還是老老實實跟 arco-design-vue 一樣(其實就是直接copy它)把 message-ul 組件拆分。
ps:后來我想了想,那可是字節(jié)哎,里面的大佬這么寫肯定是有原因的,我老老實實照著抄就行了,居然妄想組合操作。不過實踐檢驗真理,我也算是.....好吧,我就是瞎搞了。
不嗶嗶了,看代碼吧!
message-item.vue
<template> <li class="message-item message"> <p class="title"> <img v-if="type === 'success'" src="@/assets/icons/task-sucess.png" alt="" /> <img v-if="type === 'error'" src="@/assets/icons/task-error.png" alt="" /> <img v-if="type === 'warn'" src="@/assets/icons/task-cancel.png" alt="" /> <span>{{ title }}</span> </p> <p v-if="prompt" class="name"> {{ prompt.length >= 20 ? `${prompt.slice(0, 15)}...` : prompt }} </p> <span class="close" @click="handleClose"> <IconClose /> </span> </li> </template> <script lang="ts" setup> import { onMounted, onUnmounted } from 'vue'; import { IconClose } from '@arco-design/web-vue/es/icon'; const emits = defineEmits(['close']); let timer = 0; // 每個實例?(item)有自己的定時器,這樣就不會影響其他的item了 const props = defineProps({ title: String, // 標(biāo)題 prompt: { type: String, default: undefined, }, id: { type: [String, Number], required: true, }, type: { type: String, required: true, }, duration: { type: Number, default: 3000, }, }); const clearTimer = () => { if (timer) { window.clearTimeout(timer); timer = 0; } }; function handleClose() { emits('close', props.id); } const startTimer = () => { if (props.duration > 0) { timer = window.setTimeout(handleClose, props.duration); } }; onMounted(() => { startTimer(); }); onUnmounted(() => { clearTimer(); }); </script>
message-list.vue
<template> <TransitionGroup tag="ul" class="message-box" name="list" :theme="appStore.theme"> <Message v-for="item in messages" :key="item.id" v-bind="item" @close="handleClose" /> </TransitionGroup> </template> <script lang="ts" setup> import type { PropType } from 'vue'; import { useAppStore } from '@/store'; import { MessageItem } from './types'; import Message from './message-item.vue'; const appStore = useAppStore(); const emits = defineEmits(['close']); defineProps({ messages: { type: Array as PropType<MessageItem[]>, default: () => [], }, }); function handleClose(id: string | number) { emits('close', id); } </script> <style lang="less"> // 樣式這里就放個動畫的,其他的就不放了 .list-enter-active, .list-leave-active { transition: all 0.5s ease; } .list-enter-from { opacity: 0; transform: translateY(30px); } .list-leave-to { opacity: 0; transform: translateY(-30px); } </style>
message.ts
import type { AppContext, Ref } from 'vue'; import { createVNode, render, ref, reactive } from 'vue'; import { MessageItem, MessageConfig, MessageMethod } from './types'; import messageVue from './message-list.vue'; type _MessageConfig = MessageConfig & { type: 'success' | 'error' | 'warn'; }; class MessageManger { // 定義一個集合,用于存儲所有message的id,這里使用Set是為了自動去重 private readonly messageIds: Set<number | string>; // 定義一個響應(yīng)式集合用于存儲所有message對象,用響應(yīng)式的好處,數(shù)據(jù)變試圖變,試圖變數(shù)據(jù)變 private readonly messages: Ref<MessageItem[]>; private container: HTMLElement | null; private messageCount = 0; constructor(config: _MessageConfig, appContext?: AppContext) { this.messageIds = new Set(); this.messages = ref([]); this.container = document.createElement('div'); this.container.setAttribute('class', 'my-message'); // 創(chuàng)建一個虛擬DOM:同時messages傳遞給 messageVue;處理(實現(xiàn)) messageVue 向外拋出的close、afterClose等方法, const vm = createVNode(messageVue, { messages: this.messages.value, onClose: this.remove, onAfterClose: this.destroy, }); // eslint-disable-next-line no-use-before-define if (appContext ?? Message.myContext) { // eslint-disable-next-line no-use-before-define vm.appContext = appContext ?? Message.myContext; } // 將虛擬DOM渲染到指定容器中,并將容器添加到body標(biāo)簽里 render(vm, this.container); document.body.appendChild(this.container); } add = (config: _MessageConfig) => { // 添加message時,如果沒有傳遞id就使用 messageCount 創(chuàng)建一個 this.messageCount += 1; const id = config.id ?? `message_${this.messageCount}`; if (this.messageIds.has(id)) { return this.update(id, config); } const message: MessageItem = reactive({ id, ...config }); this.messages.value.push(message); this.messageIds.add(id); return { close: () => this.remove(id), }; }; update = (id: number | string, config: _MessageConfig) => { for (let i = 0; i < this.messages.value.length; i += 1) { if (this.messages.value[i].id === id) { const resetOnUpdate = config.duration !== undefined; Object.assign(this.messages.value[i], { ...config, id, resetOnUpdate }); break; } } return { close: () => this.remove(id), }; }; remove = (id: number | string) => { for (let i = 0; i < this.messages.value.length; i += 1) { const item = this.messages.value[i]; if (item.id === id) { if (typeof item.onClose === 'function') { item.onClose(id); } this.messages.value.splice(i, 1); this.messageIds.delete(id); break; } } this.destroy(); }; clear = () => { this.messages.value.splice(0); }; destroy = () => { // 如果所有message都關(guān)閉了,那就銷毀整個實例對象,并從body中移除容器 if (this.messages.value.length === 0 && this.container) { render(null, this.container); document.body.removeChild(this.container); this.container = null; // eslint-disable-next-line no-use-before-define messageInstance = null; } }; } let messageInstance: MessageManger | null = null; const types = ['success', 'error', 'warn'] as const; const message = types.reduce((pre, value) => { pre[value] = (config: MessageConfig, appContext?: AppContext) => { const newConfig: _MessageConfig = { ...config, type: value }; if (!messageInstance) { messageInstance = new MessageManger(newConfig, appContext); } return messageInstance.add(newConfig); }; return pre; }, {} as MessageMethod); message.clear = () => { if (messageInstance) { messageInstance?.clear(); } }; const Message = { ...message, myContext: null as AppContext | null, }; // 上面這段代碼等同于 /* const message = { success: (config: MessageConfig, appContext?: AppContext) => { const newConfig: _MessageConfig = { ...config, type: 'success' }; if (!messageInstance) { messageInstance = new MessageManger(newConfig, appContext); } return messageInstance.add(newConfig); }, error: (config: MessageConfig, appContext?: AppContext) => { const newConfig: _MessageConfig = { ...config, type: 'error' }; if (!messageInstance) { messageInstance = new MessageManger(newConfig, appContext); } return messageInstance.add(newConfig); }, warn: (config: MessageConfig, appContext?: AppContext) => { const newConfig: _MessageConfig = { ...config, type: 'warn' }; if (!messageInstance) { messageInstance = new MessageManger(newConfig, appContext); } return messageInstance.add(newConfig); }, clear: () => { if (messageInstance) { messageInstance?.clear(); } }, }; */ // 拋出去的是一個對象,每次調(diào)用 message.xxx() 都是調(diào)用的同一個對象,所以這是一個單例模式 export default message;
types.ts
import type { AppContext } from 'vue'; export interface MessageItem { id: number | string; title: string; prompt?: string; type: 'success' | 'error' | 'warn'; duration?: number; closable?: boolean; onClose?: (id: number | string) => void; } export interface MessageConfig { prompt?: string; title: string; id?: string; closable?: boolean; duration?: number; onClose?: (id: number | string) => void; type?: 'success' | 'error' | 'warn'; } export interface MessageReturn { close: () => void; } export interface MessageMethod { success: (config: MessageConfig, appContext?: AppContext) => MessageReturn; error: (config: MessageConfig, appContext?: AppContext) => MessageReturn; warn: (config: MessageConfig, appContext?: AppContext) => MessageReturn; remove: (id: string) => void; clear: () => void; }
到此,一個使用Vue3+TS實現(xiàn)的簡單版的Message組件就完成了。如果有更好的實現(xiàn)方法,歡迎在評論區(qū)討論。同時也歡迎各位大佬指出我的不足
以上就是基于Vue3+ts封裝一個簡單版的Message組件的詳細內(nèi)容,更多關(guān)于Vue3+ts封裝Message組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue.js 3.x 中的響應(yīng)式數(shù)據(jù)ref 與 reactive詳解
ref 和 reactive 是 Vue.js 3 中用于創(chuàng)建響應(yīng)式數(shù)據(jù)的兩個關(guān)鍵函數(shù),它們分別適用于不同類型的數(shù)據(jù),幫助我們更好地組織和管理組件的狀態(tài),這篇文章主要介紹了Vue.js 3.x 中的響應(yīng)式數(shù)據(jù):ref 與 reactive,需要的朋友可以參考下2024-01-01vue項目實現(xiàn)路由跳轉(zhuǎn)到新頁面,返回舊頁面,保留之前的數(shù)據(jù)記錄(操作代碼)
這篇文章主要介紹了vue項目實現(xiàn)路由跳轉(zhuǎn)到新頁面,返回舊頁面,保留之前的數(shù)據(jù)記錄,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09關(guān)于elementUI select控件綁定多個值(對象)
這篇文章主要介紹了關(guān)于elementUI select控件綁定多個值(對象),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Vue3 響應(yīng)式高階用法之triggerRef()的使用
在Vue3響應(yīng)式系統(tǒng)中,shallowRef僅追蹤頂層屬性的變化,當(dāng)需要對內(nèi)層屬性作出反應(yīng)時,可使用triggerRef()方法手動觸發(fā)更新,本文介紹了triggerRef()的應(yīng)用場景、基本用法、功能和最佳實踐,感興趣的可以了解一下2024-09-09