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

Vue彈窗Dialog最佳使用方案實戰(zhàn)

 更新時間:2023年11月07日 09:26:39   作者:youth君  
這篇文章主要為大家介紹了極度舒適的Vue彈窗Dialog最佳使用方案實戰(zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

一個Hook讓你體驗極致舒適的Dialog使用方式

GitHub倉庫

Dialog地獄

為啥是地獄?

因為凡是有Dialog出現(xiàn)的頁面,其代碼絕對優(yōu)雅不起來!因為一旦你在也個組件中引入Dialog,就最少需要額外維護一個visible變量。如果只是額外維護一個變量這也不是不能接受,可是當同樣的Dialog組件,即需要在父組件控制它的展示與隱藏,又需要在子組件中控制。

為了演示我們先實現(xiàn)一個MyDialog組件

<script setup lang="ts">
import { computed } from 'vue';
import { ElDialog } from 'element-plus';
const props = defineProps<{
  visible: boolean;
  title?: string;
}>();
const emits = defineEmits<{
  (event: 'update:visible', visible: boolean): void;
  (event: 'close'): void;
}>();
const dialogVisible = computed<boolean>({
  get() {
    return props.visible;
  },
  set(visible) {
    emits('update:visible', visible);
    if (!visible) {
      emits('close');
    }
  },
});
</script>
<template>
  <ElDialog v-model="dialogVisible" :title="title" width="30%">
    <span>This is a message</span>
    <template #footer>
      <span>
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogVisible = false"> Confirm </el-button>
      </span>
    </template>
  </ElDialog>
</template>

演示場景

就像下面這樣:

示例代碼如下:

<script setup lang="ts">
import { ref } from 'vue';
import { ElButton } from 'element-plus';
import Comp from './components/Comp.vue';
import MyDialog from './components/MyDialog.vue';
const dialogVisible = ref<boolean>(false);
const dialogTitle = ref<string>('');
const handleOpenDialog = () => {
  dialogVisible.value = true;
  dialogTitle.value = '父組件彈窗';
};
const handleComp1Dialog = () => {
  dialogVisible.value = true;
  dialogTitle.value = '子組件1彈窗';
};
const handleComp2Dialog = () => {
  dialogVisible.value = true;
  dialogTitle.value = '子組件2彈窗';
};
</script>
<template>
  <div>
    <ElButton @click="handleOpenDialog"> 打開彈窗 </ElButton>
    <Comp text="子組件1" @submit="handleComp1Dialog"></Comp>
    <Comp text="子組件2" @submit="handleComp2Dialog"></Comp>
    <MyDialog v-model:visible="dialogVisible" :title="dialogTitle"></MyDialog>
  </div>
</template>

這里的MyDialog會被父組件和兩個Comp組件都會觸發(fā),如果父組件并不關心子組件的onSubmit事件,那么這里的submit在父組件里唯一的作用就是處理Dialog的展示?。?!??這樣真的好嗎?不好!

來分析一下,到底哪里不好!

MyDialog本來是submit動作的后續(xù)動作,所以理論上應該將MyDialog寫在Comp組件中。但是這里為了管理方便,將MyDialog掛在父組件上,子組件通過事件來控制MyDialog。

再者,這里的handleComp1DialoghandleComp2Dialog函數(shù)除了處理MyDialog外,對于父組件完全沒有意義卻寫在父組件里。

如果這里的Dialog多的情況下,簡直就是Dialog地獄啊!??

理想的父組件代碼應該是這樣:

<script setup lang="ts">
import { ElButton } from 'element-plus';
import Comp from './components/Comp.vue';
import MyDialog from './components/MyDialog.vue';
const handleOpenDialog = () => {
  // 處理 MyDialog
};
</script>
<template>
  <div>
    <ElButton @click="handleOpenDialog"> 打開彈窗 </ElButton>
    <Comp text="子組件1"></Comp>
    <Comp text="子組件2"></Comp>
  </div>
</template>

在函數(shù)中處理彈窗的相關邏輯才更合理。

解決之道

??朕觀之,是書之文或不雅,致使人之心有所厭,何得無妙方可解決?

依史記之辭曰:“天下苦Dialog久矣,苦楚深深,望有解脫之道。”于是,諸位賢哲紛紛舉起討伐Dialog之旌旗,終“命令式Dialog”逐漸突破困境之境地。

沒錯現(xiàn)在網(wǎng)上對于Dialog的困境,給出的解決方案基本上就“命令式Dialog”看起來比較優(yōu)雅!這里給出幾個網(wǎng)上現(xiàn)有的命令式Dialog實現(xiàn)。

命令式一

吐槽一下~,這種是能在函數(shù)中處理彈窗邏輯,但是缺點是MyDialog組件與showMyDialog是兩個文件,增加了維護的成本。

命令式二

基于第一種實現(xiàn)的問題,不就是想讓MyDialog.vue.js文件合體嗎?于是諸位賢者想到了JSX。于是進一步的實現(xiàn)是這樣:

嗯,這下完美了!??

完美?還是要吐槽一下~

  • 如果我的系統(tǒng)中有很多彈窗,難道要給每個彈窗都寫成這樣嗎?
  • 這種兼容JSX的方式,需要引入支持JSX的依賴!
  • 如果工程中不想即用template又用JSX呢?
  • 如果已經(jīng)存在使用template的彈窗了,難道推翻重寫嗎?
  • ...

思考

首先承認一點命令式的封裝的確可以解決問題,但是現(xiàn)在的封裝都存一定的槽點。

如果有一種方式,即保持原來對話框的編寫方式不變,又不需要關心JSXtemplate的問題,還保存了命令式封裝的特點。這樣是不是就完美了?

那真的可以同時做到這些嗎?

如果存在一個這樣的Hook可以將狀態(tài)驅動的Dialog,轉換為命令式的Dialog嗎,那不就行了?

它來了:useCommandComponent

父組件這樣寫:

<script setup lang="ts">
import { ElButton } from 'element-plus';
import { useCommandComponent } from '../../hooks/useCommandComponent';
import Comp from './components/Comp.vue';
import MyDialog from './components/MyDialog.vue';
const myDialog = useCommandComponent(MyDialog);
</script>
<template>
  <div>
    <ElButton @click="myDialog({ title: '父組件彈窗' })"> 打開彈窗 </ElButton>
    <Comp text="子組件1"></Comp>
    <Comp text="子組件2"></Comp>
  </div>
</template>

Comp組件這樣寫:

<script setup lang="ts">
import { ElButton } from 'element-plus';
import { useCommandComponent } from '../../../hooks/useCommandComponent';
import MyDialog from './MyDialog.vue';
const myDialog = useCommandComponent(MyDialog);
const props = defineProps<{
  text: string;
}>();
</script>
<template>
  <div>
    <span>{{ props.text }}</span>
    <ElButton @click="myDialog({ title: props.text })">提交(需確認)</ElButton>
  </div>
</template>

對于MyDialog無需任何改變,保持原來的樣子就可以了!

useCommandComponent真的做到了,即保持原來組件的編寫方式,又可以實現(xiàn)命令式調(diào)用!

使用效果:

是不是感受到了莫名的舒適?

不過別急??,要想體驗這種極致的舒適,你的Dialog還需要遵循兩個約定!

兩個約定

如果想要極致舒適的使用useCommandComponent,那么彈窗組件的編寫就需要遵循一些約定(其實這些約定應該是彈窗組件的最佳實踐)。

約定如下:

  • 彈窗組件的props需要有一個名為visible的屬性,用于驅動彈窗的打開和關閉。
  • 彈窗組件需要emit一個close事件,用于彈窗關閉時處理命令式彈窗。

如果你的彈窗組件滿足上面兩個約定,那么就可以通過useCommandComponent極致舒適的使用了??!

這兩項約定雖然不是強制的,但是這確實是最佳實踐!不信你去翻所有的UI框看看他們的實現(xiàn)。我一直認為學習和生產(chǎn)中多學習優(yōu)秀框架的實現(xiàn)思路很重要!

如果不遵循約定

這時候有的同學可能會說:哎嘿,我就不遵循這兩項約定呢?我的彈窗就是要標新立異的不用visible屬性來控制打開和關閉,我起名為dialogVisible呢?我的彈窗就是沒有close事件呢?我的事件是具有業(yè)務意義的submit、cancel呢?...

得得得,如果真的沒有遵循上面的兩個約定,依然可以舒適的使用useCommandComponent,只不過在我看來沒那么極致舒適!雖然不是極致舒適,但也要比其他方案舒適的多!

如果你的彈窗真的沒有遵循“兩個約定”,那么你可以試試這樣做:

<script setup lang="ts">
// ...
const myDialog = useCommandComponent(MyDialog);

const handleDialog = () => {
  myDialog({ 
    title: '父組件彈窗', 
    dialogVisible: true, 
    onSubmit: () => myDialog.close(),
    onCancel: () => myDialog.close(),
  });
};
</script>

<template>
  <div>
    <ElButton @click="handleDialog"> 打開彈窗 </ElButton>
    <!--...-->
  </div>
</template>

如上,只需要在調(diào)用myDialog函數(shù)時在props中將驅動彈窗的狀態(tài)設置為true,在需要關閉彈窗的事件中調(diào)用myDialog.close()即可!

這樣是不是看著雖然沒有上面的極致舒適,但是也還是挺舒適的?

源碼與實現(xiàn)

實現(xiàn)思路

對于useCommandComponent的實現(xiàn)思路,依然是命令式封裝。相比于上面的那兩個實現(xiàn)方式,useCommandComponent是將組件作為參數(shù)傳入,這樣保持組件的編寫習慣不變。并且useCommandComponent遵循單一職責原則,只做好組件的掛載和卸載工作,提供足夠的兼容性。

其實useCommandComponent有點像React中的高階組件的概念

源碼

Github源碼地址

源碼不長,也很好理解!在實現(xiàn)useCommandComponent的時候源碼如下:

import { AppContext, Component, ComponentPublicInstance, createVNode, getCurrentInstance, render, VNode } from 'vue';
export interface Options {
  visible?: boolean;
  onClose?: () => void;
  appendTo?: HTMLElement | string;
  [key: string]: unknown;
}
export interface CommandComponent {
  (options: Options): VNode;
  close: () => void;
}
const getAppendToElement = (props: Options): HTMLElement => {
  let appendTo: HTMLElement | null = document.body;
  if (props.appendTo) {
    if (typeof props.appendTo === 'string') {
      appendTo = document.querySelector<HTMLElement>(props.appendTo);
    }
    if (props.appendTo instanceof HTMLElement) {
      appendTo = props.appendTo;
    }
    if (!(appendTo instanceof HTMLElement)) {
      appendTo = document.body;
    }
  }
  return appendTo;
};
const initInstance = <T extends Component>(
  Component: T,
  props: Options,
  container: HTMLElement,
  appContext: AppContext | null = null
) => {
  const vNode = createVNode(Component, props);
  vNode.appContext = appContext;
  render(vNode, container);
  getAppendToElement(props).appendChild(container);
  return vNode;
};
export const useCommandComponent = <T extends Component>(Component: T): CommandComponent => {
  const appContext = getCurrentInstance()?.appContext;
  // 補?。篊omponent中獲取當前組件樹的provides
  if (appContext) {
    const currentProvides = (getCurrentInstance() as any)?.provides;
    Reflect.set(appContext, 'provides', {...appContext.provides, ...currentProvides});
  }
  const container = document.createElement('div');
  const close = () => {
    render(null, container);
    container.parentNode?.removeChild(container);
  };
  const CommandComponent = (options: Options): VNode => {
    if (!Reflect.has(options, 'visible')) {
      options.visible = true;
    }
    if (typeof options.onClose !== 'function') {
      options.onClose = close;
    } else {
      const originOnClose = options.onClose;
      options.onClose = () => {
        originOnClose();
        close();
      };
    }
    const vNode = initInstance<T>(Component, options, container, appContext);
    const vm = vNode.component?.proxy as ComponentPublicInstance<Options>;
    for (const prop in options) {
      if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) {
        vm[prop as keyof ComponentPublicInstance] = options[prop];
      }
    }
    return vNode;
  };
  CommandComponent.close = close;
  return CommandComponent;
};
export default useCommandComponent;

除了命令式的封裝外,我加入了const appContext = getCurrentInstance()?.appContext;。這樣做的目的是,傳入的組件在這里其實已經(jīng)獨立于應用的Vue上下文了。為了讓組件依然保持和調(diào)用方相同的Vue上下文,我這里加入了獲取上下文的操作!

基于這個情況,在使用useCommandComponent時需要保證它在setup中被調(diào)用,而不是在某個點擊事件的處理函數(shù)中哦~

源碼補丁

非常感謝@bluryar關于命令式組件無法獲取當前組件樹的 injection 的指出!!

趁著熱乎,我想到一個解決獲取當前injection的解決辦法。那就是將當前組件樹的providesappContext.provides合并,這樣傳入的彈窗組件就可以順利的獲取到app和當前組件樹的provides了!

以上就是Vue彈窗Dialog最佳使用方案實戰(zhàn)的詳細內(nèi)容,更多關于Vue Dialog彈窗的資料請關注腳本之家其它相關文章!

相關文章

  • vue中移動端調(diào)取本地的復制的文本方式

    vue中移動端調(diào)取本地的復制的文本方式

    這篇文章主要介紹了vue中移動端調(diào)取本地的復制的文本方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • vue-draggable實現(xiàn)拖拽表單的示例代碼

    vue-draggable實現(xiàn)拖拽表單的示例代碼

    本文主要介紹了vue-draggable實現(xiàn)拖拽表單的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-05-05
  • vue頁面不能根據(jù)路徑進行跳轉的解決方法

    vue頁面不能根據(jù)路徑進行跳轉的解決方法

    本文主要介紹了vue頁面不能根據(jù)路徑進行跳轉的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-12-12
  • Vue實現(xiàn)virtual-dom的原理簡析

    Vue實現(xiàn)virtual-dom的原理簡析

    這篇文章主要介紹了Vue實現(xiàn)virtual-dom的原理簡析,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • 解析vue中的$mount

    解析vue中的$mount

    本文主要是帶領大家分析$mount的相關知識,需要的朋友一起學習吧
    2017-12-12
  • 淺談ElementUI el-select 數(shù)據(jù)過多解決辦法

    淺談ElementUI el-select 數(shù)據(jù)過多解決辦法

    下拉框的選項很多,上萬個選項甚至更多,這個時候如果全部把數(shù)據(jù)放到下拉框中渲染出來,瀏覽器會卡死,體驗會特別不好,本文主要介紹了ElementUI el-select 數(shù)據(jù)過多解決辦法,感興趣的可以了解一下
    2021-09-09
  • vue中如何初始化data數(shù)據(jù)

    vue中如何初始化data數(shù)據(jù)

    這篇文章主要介紹了vue中如何初始化data數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 解決vue+webpack項目接口跨域出現(xiàn)的問題

    解決vue+webpack項目接口跨域出現(xiàn)的問題

    這篇文章主要介紹了解決vue+webpack項目接口跨域出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • Vue觸發(fā)隱藏input file的方法實例詳解

    Vue觸發(fā)隱藏input file的方法實例詳解

    這篇文章主要介紹了Vue觸發(fā)隱藏input file的方法實例詳解,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下
    2019-08-08
  • 使用vue.js在頁面內(nèi)組件監(jiān)聽scroll事件的方法

    使用vue.js在頁面內(nèi)組件監(jiān)聽scroll事件的方法

    今天小編就為大家分享一篇使用vue.js在頁面內(nèi)組件監(jiān)聽scroll事件的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09

最新評論