欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3封裝組件完整實(shí)例(帶回調(diào)事件)

 更新時間:2023年06月20日 10:59:28   作者:前端咸魚翻身  
Vue.js已成為現(xiàn)代Web開發(fā)中不可或缺的技術(shù)之一,雖然Vue.js的一些基礎(chǔ)概念和語法比較易學(xué),但深入挖掘Vue.js的核心概念和功能需要更多的實(shí)踐,下面這篇文章主要給大家介紹了關(guān)于Vue3封裝組件(帶回調(diào)事件)的相關(guān)資料,需要的朋友可以參考下

前言

這篇文章接著上一篇文章(Vue3封裝全局函數(shù)式組件)繼續(xù)分享vue3的組件封裝,上一篇文章是以Toast提示為例子封裝的組件,此外還有Dialog 彈出框這種組件我們常常會封裝起來調(diào)用,它們之間區(qū)別不大,主要就是多了點(diǎn)擊按鈕能觸發(fā)回調(diào),所以這篇文章介紹一下函數(shù)式組件中回調(diào)事件的寫法,希望對大家有所啟發(fā)。

一、思路

首先組件調(diào)用需要滿足鏈?zhǔn)秸{(diào)用,想實(shí)現(xiàn)形如以下的寫法:

okToast.show()
  .then(res => {
    console.log('點(diǎn)擊了確認(rèn)');
  })
  .catch(err => {
    console.log('點(diǎn)擊了取消');
  })

在 then 后面處理的是點(diǎn)擊確認(rèn)的邏輯,在 catch 后面處理的是點(diǎn)擊取消的邏輯,并且在 then 后面可以繼續(xù)調(diào)起第二個彈窗滿足鏈?zhǔn)秸{(diào)用。
那么很自然能想到的就是采用 Promise 實(shí)現(xiàn),Promise 可以把異步操作執(zhí)行后的狀態(tài)及時傳遞回來使得回調(diào)函數(shù)能夠及時調(diào)用。

二、代碼示例

1. vue文件

代碼如下(示例):

<template>
  <transition name="toast" @after-leave="onAfterLeave">
    <div class="toast" v-if="isShow" :style="{ width: toastWidth }">
      <div
        v-if="time < 0 && type !== 'confirm' && type !== 'confirmAndcancel'"
        class="cancel"
        @click="hidden"
      ></div>
      <img
        v-if="type === 'success' || type === 'icon'"
        class="img"
        src="../../assets/images/7vip_web_toast_finish_icon_40x40@2x.png"
        alt="success"
      />
      <img
        v-if="type === 'warn'"
        class="img"
        src="../../assets/images/7vip_web_toast_warn.png"
        alt="warn"
      />
      <div v-if="content && type !== 'icon'" class="content" :style="{ textAlign }">
        {{ content }}
      </div>
      <!-- 主要在這里增加了兩種樣式 -->
      <!-- 這是只有一個確定按鈕的 -->
      <div class="operation" v-if="type === 'confirm'">
        <div class="confirm" @click="successHandle">{{ successText }}</div>
      </div>
      <!-- 這是同時有確定與取消按鈕的 -->
      <div class="operation" v-if="type === 'confirmAndcancel'">
        <div class="close" @click="cancelHandle">{{ cancelText }}</div>
        <div class="confirm" @click="successHandle">{{ successText }}</div>
      </div>
    </div>
  </transition>
</template>
<script setup>
import { ref, computed, nextTick } from 'vue';
const props = defineProps({
  content: {
    type: String,
    default: 'success'
  },
  time: {
    type: Number,
    default: 2000
  },
  width: {
    default: 310
  },
  textAlign: {
    type: String,
    default: 'center'
  },
  type: {
    type: String,
    default: 'success'
  },
  hide: {
    type: Function,
    default: () => {}
  },
  successText: {
    type: String,
    default: '確認(rèn)'
  },
  cancelText: {
    type: String,
    default: '取消'
  },
  successBtn: {
    type: Function,
    default: () => {}
  },
  cancelBtn: {
    type: Function,
    default: () => {}
  }
});
const isShow = ref(false);
const toastWidth = computed(
  () => (parseInt(props.width.toString()) / 750) * document.documentElement.clientWidth + 'px'
);
const show = () => {
  isShow.value = true;
  if (props.time >= 0) {
    setTimeout(() => {
      // isShow.value = false;
      successHandle();
    }, props.time);
  }
};
defineExpose({
  show
});
const hidden = () => {
  isShow.value = false;
};
const onAfterLeave = () => {
  props.hide();
};
//新增處理確認(rèn)的方法
const successHandle = () => {
  props.successBtn();
  nextTick(() => {
    hidden();
  });
};
//新增的處理取消的方法
const cancelHandle = () => {
  props.cancelBtn();
  nextTick(() => {
    hidden();
  });
};
</script>
<style lang="scss" scoped>
.toast-enter-active,
.toast-leave-active {
  transition: opacity 0.3s ease-out;
}
.toast-enter-from,
.toast-leave-to {
  opacity: 0;
}
.toast {
  position: fixed;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 99;
  background: #333333;
  border-radius: 20px;
  padding: 20px;
  text-align: center;
  .cancel {
    background: url('../../assets/images/quxiao@2x.png') no-repeat center / contain;
    position: absolute;
    top: 10px;
    right: 10px;
    width: 20px;
    height: 20px;
    &::before {
      content: '';
      position: absolute;
      top: -10px;
      right: -10px;
      bottom: -10px;
      left: -10px;
    }
  }
  .img {
    width: 40px;
    height: 40px;
  }
  .content {
    margin-top: 10px;
    font-size: 16px;
    color: #ffcc99;
    text-align: initial;
  }
  .operation {
    display: flex;
    justify-content: space-around;
    align-items: center;
    margin-top: 20px;
    .confirm {
      color: white;
      opacity: 0.9;
    }
    .close {
      color: #ffcc99;
      opacity: 0.9;
    }
  }
}
</style>

說明 ①:

const successHandle = () => {
  props.successBtn();
  nextTick(() => {
    hidden();
  });
};

這里有個細(xì)節(jié),在 vue 文件里的 successHandle 方法里由于點(diǎn)擊后需要關(guān)閉彈窗,所以調(diào)用 hidden 方法,但是得先執(zhí)行 successBtn 方法,即 Promise 里的 resolve() ,然后處理完回調(diào)后的邏輯再進(jìn)行關(guān)閉卸載的邏輯,否則可能回調(diào)還沒執(zhí)行完彈窗就因?yàn)?hidden 的原因被關(guān)掉了,所以這個執(zhí)行順序很重要,此處加入vue中的 nextTick 方法保證了 hidden 是在所有異步任務(wù)的最后再觸發(fā)。cancelHandle 方法同理。

說明 ②:

const show = () => {
  isShow.value = true;
  if (props.time >= 0) {
    setTimeout(() => {
      // isShow.value = false;
      successHandle();
    }, props.time);
  }
};

show方法里面不直接關(guān)閉彈窗了,而是調(diào)用傳進(jìn)來的方法,這樣使得普通寫法即 proxy.$okToast() 也可以使用鏈?zhǔn)秸{(diào)用。

2. js文件

代碼如下(示例):

import { createApp } from 'vue';
import OkToast from './okToast.vue';

let rootNode = null;
let app = null;

const okToast = options => {
  const dom = document.body.querySelector('.my-dialog');
  if (!dom) {
    // 創(chuàng)建元素節(jié)點(diǎn)
    rootNode = document.createElement('div');
    rootNode.className = `my-dialog`;
    // 在body標(biāo)簽內(nèi)部插入此元素
    document.body.appendChild(rootNode);
  } else {
    app.unmount();
  }
  // 創(chuàng)建應(yīng)用實(shí)例(第一個參數(shù)是根組件。第二個參數(shù)可選,它是要傳遞給根組件的 props)
  app = createApp(OkToast, {
    ...options,
    hide() {
      // 卸載已掛載的應(yīng)用實(shí)例
      if (app) {
        app.unmount();
        app = null;
      }
      // 刪除rootNode節(jié)點(diǎn)
      if (rootNode) {
        document.body.removeChild(rootNode);
        rootNode = null;
      }
    }
  });
  // 將應(yīng)用實(shí)例掛載到創(chuàng)建的 DOM 元素上
  return app.mount(rootNode);
};

// 需要給options設(shè)默認(rèn)值,否則直接調(diào)用okToast()會出錯
const okFun = (options = {}) => {
  return new Promise((resolve, reject) => {
    options.successBtn = () => {
      resolve();
    };
    options.cancelBtn = () => {
      reject();
    };
    okToast(options).show();
  });
};

okToast.install = app => {
  // 注冊全局組件
  // app.component("Toast", OkToast);
  // 注冊全局屬性,類似于 Vue2 的 Vue.prototype
  // app.config.globalProperties.$okToast = options => okToast(options).show();
  app.config.globalProperties.$okToast = options => okFun(options);
};
// 定義show方法用于直接調(diào)用
// okToast.show = options => okToast(options).show();
okToast.show = options => okFun(options);

export default okToast;

說明 ①:

原本的寫法是okToast(options).show(),現(xiàn)在是調(diào)用okFun方法,在okFun方法里返回的是一個 Promise 對象,并且利用 createApp 方法其第二個參數(shù)是可以傳遞給根組件的,那么就能往參數(shù)上定義兩個方法,這兩個方法上分別綁定在確定及取消按鈕上,當(dāng)按鈕點(diǎn)擊后觸發(fā)此處事件那么就能改變 Promise 的狀態(tài)從而觸發(fā)回調(diào)了,very interesting!

三、使用方法及效果展示

傳入 type 為 “confirmAndcancel” 則有兩個按鈕,傳入 type 為 “confirm” 則只有一個,time 需要傳 -1 以便彈窗停留在窗口,詳細(xì)參數(shù)可看上面的 vue 文件里的props。同時由于 Promise 支持 finally 方法,所以作為示例也加上了。代碼如下(示例):

proxy
  .$okToast({
    time: -1,
    width: 500,
    type: 'confirmAndcancel',
    content: '如果解決方法是丑陋的,那就肯定還有更好的解決方法,只是還沒有發(fā)現(xiàn)而已。',
  })
  .then(res => {
    console.log('點(diǎn)擊了確認(rèn)1');
    proxy
      .$okToast({
        time: -1,
        width: 500,
        type: 'confirm',
        content: '再點(diǎn)一下彈窗就消失了',
        successText: 'OK'
      })
      .then(res => {
        console.log('點(diǎn)擊了確認(rèn)2');
      });
  })
  .catch(err => {
    console.log('點(diǎn)擊了取消');
    proxy.$okToast({
      time: -1,
      width: 500,
      type: 'confirm',
      content: '再點(diǎn)一下彈窗就消失了',
      successText: 'OK',
    });
  })
  .finally(() => {
    console.log('finally');
  });

效果展示:

請?zhí)砑訄D片描述

可以看到鏈?zhǔn)秸{(diào)用的回調(diào)是正常生效的。

四、加入一點(diǎn)細(xì)節(jié),完善效果

上面的效果大體上已經(jīng)實(shí)現(xiàn)了Dialog 的彈窗效果了,正當(dāng)我以為大功告成的時候,對比UI組件庫上的 Dialog ,我才意識到少了點(diǎn)東西。。。沒錯就是遮罩層,遮罩層除了樣式還有背景禁止?jié)L動的邏輯,好吧,趕緊補(bǔ)上去。

首先定義遮罩層的樣式,代碼如下(示例):

.my-overlay {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 99;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
}

然后創(chuàng)建彈窗 DOM 的時候把遮罩層元素也添加進(jìn)去。一開始想法很簡單也實(shí)現(xiàn)了效果,就是直接監(jiān)聽元素的 “touchmove” 事件阻止?jié)L動,比如這樣:

let overlayNode = null;
let rootNode = null;
let app = null;
// 阻止默認(rèn)事件
let noScroll = e => {
  e.preventDefault();
};
const okToast = options => {
  const dom = document.body.querySelector(".my-dialog");
  if (!dom) {
    if (options.type === "confirmAndcancel" || options.type === "confirm") {
      // 創(chuàng)建遮罩層
      overlayNode = document.createElement("div");
      overlayNode.className = `my-overlay`;
      document.body.appendChild(overlayNode);
      // 監(jiān)聽滾動事件
      document.body.querySelector(".my-overlay").addEventListener("touchmove", noScroll);
    }
    rootNode = document.createElement("div");
    rootNode.className = `my-dialog`;
    document.body.appendChild(rootNode);
    // 監(jiān)聽滾動事件
    document.body.querySelector(".my-dialog").addEventListener("touchmove", noScroll);
  } else {
    app.unmount();
  }
  app = createApp(OkToast, {
    ...options,
    hide() {
      if (options.type === "confirmAndcancel" || options.type === "confirm") {
        // 解除監(jiān)聽
        document.body.querySelector(".my-overlay").removeEventListener("touchmove", noScroll);
        document.body.querySelector(".my-dialog").removeEventListener("touchmove", noScroll);
        if (overlayNode) {
          document.body.removeChild(overlayNode);
          overlayNode = null;
        }
      }
      if (app) {
        app.unmount();
        app = null;
      }
      if (rootNode) {
        document.body.removeChild(rootNode);
        rootNode = null;
      }
    },
  });
  return app.mount(rootNode);
};

存在的問題:

但是這樣的話,不僅把背景禁止?jié)L動了也把彈窗里的內(nèi)容給禁止?jié)L動了,如果彈窗文字內(nèi)容太多,那彈窗高度就需要限制,彈窗內(nèi)容做滾動處理,而在考慮這種背景不能滾動而彈窗內(nèi)能滾動的情況時,遇到了移動端的滾動背景穿透問題,又得考慮兼容性問題,還真是一波三折。

解決辦法:

嘗試了各種寫法后,還好最終找到了一種比較簡單的寫法實(shí)現(xiàn)了效果,就是給同級的 Vue 創(chuàng)建的 app 元素 加 position = "fixed",彈窗出現(xiàn)的時候固定定位使得背景層頁面不能滾動, bottom 值定位在當(dāng)前頁面滾動距離免得頁面位置變化,最后彈窗消失的時候取消 fixed 定位,這個時候頁面會自動為于頂部,不過沒關(guān)系,再將頁面的 scrollTop 值還原回去就可以了,這種方法雖然不能保證100%成功,但是也經(jīng)過了我手上安卓及蘋果機(jī)的測試,如果遇到問題歡迎在評論區(qū)指出。

代碼如下(示例):

let overlayNode = null;
let rootNode = null;
let app = null;
let scrollTop = 0;
const createOverlay = () => {
  const app= document.querySelector('#app');
  if (!scrollTop) {
    scrollTop = app.scrollTop;
  }
  // 禁止app元素滾動
  app.style.position = 'fixed';
  app.style.bottom = scrollTop + 'px';
  // 兼容ios手機(jī)fixed定位不生效,得加overflow
  app.style.overflow = 'visible';
  // 創(chuàng)建遮罩層
  overlayNode = document.createElement('div');
  overlayNode.className = `my-overlay`;
  // // 在body標(biāo)簽內(nèi)部插入此元素
  document.body.appendChild(overlayNode);
};
const deleteOverlay = () => {
  // 刪除overlayNode節(jié)點(diǎn)
  if (overlayNode) {
    document.body.removeChild(overlayNode);
    overlayNode = null;
  }
  // 解除app元素滾動,移除樣式
  const app =document.querySelector('#app')
  app.style.removeProperty('position')
  app.style.removeProperty('bottom')
  app.style.removeProperty('overflow')
  // 恢復(fù)頁面滾動距離
  app.scrollTop = scrollTop;
  // 恢復(fù)默認(rèn)值
  scrollTop = 0;
};
const okToast = options => {
  const dom = document.body.querySelector('.my-dialog');
  if (!dom) {
  	// 將type與遮罩層關(guān)系解耦,根據(jù)傳入的time參數(shù)確定遮罩層顯示或隱藏,time小于0則是Dialog,大于0或者不傳time則是Toast,當(dāng)然更好的方式是區(qū)分開這兩個組件,然后單獨(dú)做配置項,這里只是為了兼容之前的Toast邏輯
    // if (options.type === "confirmAndcancel" || options.type === "confirm") {
    if (options.time && options.time < 0) {
      createOverlay();
    }
    // 創(chuàng)建元素節(jié)點(diǎn)
    rootNode = document.createElement('div');
    rootNode.className = `my-dialog`;
    document.body.appendChild(rootNode);
  } else {
    app.unmount();
    // 根據(jù)傳入配置去掉遮罩層
    if (!options.time || options.time > 0) {
      deleteOverlay();
    }
  }
  app = createApp(OkToast, {
    ...options,
    hide() {
      if (options.time && options.time < 0) {
        deleteOverlay();
      }
      if (app) {
        app.unmount();
        app = null;
      }
      if (rootNode) {
        document.body.removeChild(rootNode);
        rootNode = null;
      }
    }
  });
  // 將應(yīng)用實(shí)例掛載到創(chuàng)建的 DOM 元素上
  return app.mount(rootNode);
};

另外 vue 文件也改一下樣式,滿足內(nèi)容區(qū)的滾動效果

.content {
  margin-top: 10px;
  font-size: 32px;
  color: #ffcc99;
  text-align: initial;
  max-height: 50vh;
  overflow-y: scroll;
}

效果展示:

請?zhí)砑訄D片描述

總結(jié)

以上就是全部內(nèi)容,本文通過封裝 Dialog 彈出框組件探索了 Vue3 函數(shù)式組件的封裝方法,同時解決了移動端彈出框背景滾動的相關(guān)問題。在此次探索學(xué)習(xí)的過程中,我深刻體會到在遇到問題的時候,可以多轉(zhuǎn)換下思路,多嘗試,同時找找資料,學(xué)習(xí)一下別人的經(jīng)驗(yàn)和技巧,因?yàn)橛行┢毡榇嬖诘膯栴}肯定有前人遇到過,那么站在巨人的肩膀上,加上自己的思考,問題就迎刃而解了。

到此這篇關(guān)于Vue3封裝組件的文章就介紹到這了,更多相關(guān)Vue3封裝組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Vue3新特性構(gòu)建動態(tài)表單的方法詳解

    使用Vue3新特性構(gòu)建動態(tài)表單的方法詳解

    傳統(tǒng)的表單開發(fā)通常需要編寫大量的重復(fù)代碼,例如處理用戶輸入、驗(yàn)證數(shù)據(jù)、更新 UI 等等,為了簡化開發(fā),我們可以借助 Vue 3 的新特性,例如組合式 API 和 ref 對象,所以本文我們將一起學(xué)習(xí)如何使用 Vue 3 的新特性構(gòu)建一個更加靈活、可擴(kuò)展的動態(tài)表單
    2024-06-06
  • vue實(shí)現(xiàn)雙向數(shù)據(jù)綁定

    vue實(shí)現(xiàn)雙向數(shù)據(jù)綁定

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)雙向數(shù)據(jù)綁定,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • vue前后端端口不一致的問題解決

    vue前后端端口不一致的問題解決

    本文主要介紹了vue前后端端口不一致的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-10-10
  • vue實(shí)現(xiàn)表格合并功能

    vue實(shí)現(xiàn)表格合并功能

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)表格合并功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • vue watch偵聽器有無immediate的運(yùn)行順序問題

    vue watch偵聽器有無immediate的運(yùn)行順序問題

    這篇文章主要介紹了vue watch偵聽器有無immediate的運(yùn)行順序問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • 使用vue-cli打包過程中的步驟以及問題的解決

    使用vue-cli打包過程中的步驟以及問題的解決

    這篇文章主要介紹了使用vue-cli打包過程中的步驟以及問題的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • 解決Vue.js Devtools未檢測到Vue實(shí)例的問題

    解決Vue.js Devtools未檢測到Vue實(shí)例的問題

    在開發(fā)Vue.js應(yīng)用時,Vue.js Devtools是一個不可或缺的調(diào)試工具,然而,有時我們可能會遇到“Vue.js not detected”的提示,這意味著Vue.js Devtools未能成功識別和連接到我們的Vue應(yīng)用,本文將詳細(xì)解析這個問題,并提供相應(yīng)的解決步驟與代碼示例,需要的朋友可以參考下
    2024-01-01
  • vue項目webpack中配置src路徑別名及使用方式

    vue項目webpack中配置src路徑別名及使用方式

    這篇文章主要介紹了vue項目webpack中配置src路徑別名及使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • Vue 動態(tài)生成數(shù)據(jù)字段的實(shí)例

    Vue 動態(tài)生成數(shù)據(jù)字段的實(shí)例

    這篇文章主要介紹了Vue 動態(tài)生成數(shù)據(jù)字段的實(shí)例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue淺析講解動態(tài)組件與緩存組件及異步組件的使用

    Vue淺析講解動態(tài)組件與緩存組件及異步組件的使用

    這篇文章主要介紹了Vue開發(fā)中的動態(tài)組件與緩存組件及異步組件的使用教程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09

最新評論