這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
在使用vue專案撰寫的時候,不可避免的會碰到需要時js api來呼叫組件進行顯示的情況
例如餓了么element ui 的 Notification 通知、Message 訊息提示等組件
雖然已經提供了,但是由于api的限制,我們只能通過特定的引數來有限的改變組件的樣式
之前的文章說過可以使用 new Vue()、Vue.extends等方法來進行改變這些api組件的樣式
但是同時它們也有個缺點,無法自動實時更新資料,也就是沒有雙向系結,只能靜態布局,為了解決這個痛點
我們自己動手封裝一個全域js api呼叫組件,然后再把需要的資料傳過去進去更新,自己動手豐衣足食
就以餓了么element-ui的通知組件Notification為例,實作一個全域通知彈窗下載進度條組件
正文
使用Vue.extend構造器來創建是最為方便的,不過和之前不同的是,這樣創建的實體組件只能創建單個,每一次呼叫都會重新創建一個新的實體,不會對原有的實體進行更新,所以我,我們要對實體進行快取,以便后續的資料更新
這里我以自定義創建一個下載進度彈窗通知為例
首先我們創建一個組件檔案夾下的js檔案
/components/DownLoadNotification/index.js
實作思路是用Vue.extend構造組件后,把api接收的引數直接傳入組件data使用,并為每個實體生成id,拿出dom插入到全域body中,當生成多個實體時動態計算定位的偏移量避免組件重疊
import Vue from 'vue' import component from './index.vue' ? const DownLoadNotification = Vue.extend(component) ? const instances = [] // 實體快取串列 ? export const notify = (options) => { let instance; // 單個實體 ? options.onClose = function() { // 把洗掉當前實體的方法傳入組件內 removeNotify(instance.id) } options.onCloseAll = () => { // 把洗掉所有實體的方法傳入組件內 removeNotifyAll() } ? // 直接控制實體的data,而不是通過propsData傳入 instance = new DownLoadNotification({ data: options, }) instance.id = Date.now(); // 生成id instance.$mount(); // 掛載,生成dom節點 document.body.appendChild(instance.$el) // 把dom節點添加到body全域 instance.visible = true // 先掛載節點再顯示節點里的內容,可以出現過渡影片,而不是一開始全部顯示 ? // 計算多個實體時的偏移量 // 第一個不需要計算,把push放到回圈下面,陣列為空時不會回圈,第二次開始則會進行計算 let verticalOffset = 0 instances.forEach((item) => { verticalOffset += item.$el.offsetHeight + 16 // 每個組件高度間隔16px }) ? verticalOffset += 16 // 首次最下面的組件底部距離最底部也有16px的間隙 ? instance.verticalOffset = verticalOffset // 計算出來的偏移量賦值到組件中data ? instances.push(instance) // 快取實體 return instance } ? // 洗掉單個組件實體 function removeNotify(id) { const index = instances.findIndex(item => item.id === id) index !== -1 && instances.splice(index, 1) } ? // 洗掉所有組件實體 function removeNotifyAll() { for (let i = instances.length - 1; i >= 0; i--) { instances[i].close(); // 呼叫組件內的洗掉方法來同時洗掉實體和dom } }
洗掉時既要清空組件dom又要洗掉實體,所以把在js中定義的洗掉實體方法傳入組件,組件需要洗掉時呼叫即可
需要注意的是,當有多個全域組件,洗掉其中一個時,位置應當發生改變
所以洗掉其中的一個組件實體時要重新計算偏移量位置
重新改造一下 洗掉單個組件實體 的方法,大致做法就是,拿到被洗掉的當前實體的高度,然后從被洗掉實體的位置開始遍歷,后面的實體逐一洗掉被洗掉的實體高度和邊距
// 洗掉單個組件實體 function removeNotify(id) { let index = -1; const len = instances.length; // 未洗掉前陣列總長度 const instance = instances.filter((instance, i) => { // 獲取保存當前洗掉的實體 if (instance.id === id) { index = i; // 保存索引 return true; } return false; })[0]; instances.splice(index, 1); // 洗掉實體 ? if (len <= 1) return // 只有一個實體時不需要重新計算位置 const position = instance.position; // 獲取實體定位欄位 const removedHeight = instance.$el.offsetHeight; // 獲取實體高度 ? for (let i = index; i < len - 1 ; i++) { // 從被洗掉的位置開始遍歷 if (instances[i].position === position) { // 修改的位置定位是否一致 // 將后續元素的定位位置 減去 上一個洗掉的元素寬度 + 16px 的首次底部邊距 instances[i].$el.style[instance.verticalProperty] = parseInt(instances[i].$el.style[instance.verticalProperty], 10) - removedHeight - 16 + 'px'; } } }接下來在撰寫組件/components/DownLoadNotification/index.vue
<template> <el-collapse-transition> <div v-if="visible" : :style="positionStyle"> <div > <span>{{ fileName }}</span> <span>{{ fileSize }}</span> </div> <el-progress :percentage="downLoadProgress" :status="downStatus" :stroke- ></el-progress> <el-button @click="close" size="mini">關 閉</el-button> </div> </el-collapse-transition> </template> <script> export default { data() { return { /* 自定義資料 */ fileName: "", fileSize: "", downLoadProgress: 0, downStatus: "", ? /* 組件基礎資料 */ id: null, // 實體id visible: false, // 顯示控制按鈕 position: "bottom-left", // 顯示方位 verticalOffset: 0, // 位置偏移量 onClose: null, // js中傳入的洗掉當前組件方法 onCloseAll: null, // js中傳入的洗掉所有組件方法 }; }, computed: { // 默認縱向布局,定位為 左 或者 右 時邊距為10px horizontalClass() { // 實體左偏移還是右偏移 return this.position.indexOf("right") > -1 ? "right" : "left"; }, verticalProperty() { // 實體上還是下 return /^top-/.test(this.position) ? "top" : "bottom"; }, positionStyle() { // 多個實體時的偏移量 return { [this.verticalProperty]: `${this.verticalOffset}px`, }; }, }, methods: { // 銷毀當前組件 close() { this.visible = false; this.$el.addEventListener("transitionend", this.destroyElement); // 添加事件,在過渡效果結束后再銷毀組件 this.onClose(); // 呼叫外面js傳入組件的方法 }, // 銷毀所有組件 closeAll() { this.onCloseAll(); }, // 銷毀組件方法 destroyElement() { this.$el.removeEventListener("transitionend", this.destroyElement); this.$destroy(true); }, }, }; </script> <style lang="less" scoped> .DownLoadNotification { width: 300px; height: 60px; background-color: #dcdfe6; position: fixed; border-radius: 10px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04); padding: 20px; .header { color: #606266; margin-bottom: 10px; } } .right { right: 10px; } .left { left: 10px; } </style> ?
data中的自定資料的就相當于使用api傳入的引數,當我們保存實體后,可以修改這個實體內的data,來達成實時更新的效果
多個實體使用案例參考
<template> <div id="app"> <div > <el-button @click="show(1)">顯示實體1</el-button> <el-button @click="addBtn(1)">增加進度</el-button> </div> <div > <el-button @click="show(2)">顯示實體1</el-button> <el-button @click="addBtn(2)">增加進度</el-button> </div> <div > <el-button @click="show(3)">顯示實體1</el-button> <el-button @click="addBtn(3)">增加進度</el-button> </div> </div> </template> <script> import { notify } from "./components/DownLoadNotification/index.js"; export default { name: "App", data() { return { instance1: null, instance2: null, instance3: null, }; }, methods: { show(index) { this[`instance${index}`] = notify({ fileName: `測驗檔案${index}.zip`, fileSize: "100mb", downLoadProgress: 0, downStatus: "success", }) }, addBtn(index) { this[`instance${index}`].downLoadProgress += 10 }, }, } </script> <style lang="less"> #app{ display: flex; align-items: center; } .btn{ display: flex; flex-direction: column; } </style>
本文轉載于:
https://juejin.cn/post/7243725204002209852
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/555475.html
標籤:其他
上一篇:JavaScript之Object.defineProperty()
下一篇:返回列表