在 Composition API 中使用 pinia 操控 modal

之前寫過一篇在 《在 Composition API 中使用 props 與 emit 操控 modal》的筆記,那時是使用 props 跟 emit 處理外部元件與內部元件之間資料傳遞的問題。

但是當有兩個以上的 modal/彈跳視窗 的時候,第一個 modal 是開啟狀態,這時觸發第二個 modal 開啟,同時第一個 modal 要關閉。這種情形下,兩個內部元件彼此要傳遞訊息變得十分複雜,這次我試著用 pinia 來管理兩個 modal 的狀態,狀態就不再儲存在兩個 modal 元件內部。

任務說明

在 pinia 中有 isFirstOpenisSecondOpen 兩個變數,用來管理 2 個元件的開啟與關閉,預設都是 false, 內部元件則用 v-if 控制元件的顯示與否,在 false 狀態,兩個元件皆為關閉,當有一個元件被開啟,則另外一個元件的狀態則會轉為 false(預設也是 false)。

假設第一個元件是開啟狀態,isFirstOpen 的狀態為 true,第二個元件的狀態isSecondOpenfalse 。這時去開啟第二個元件,isSecondOpen 轉為 true,則isFirstOpen 轉為 false,第一個元件就會關閉,反之亦然。這樣就可以達到切換2個 modal 的目的。

檔案結構如下:

main.js 引入 pinia

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount("#app");

pinia 中 modalControl.js 的設定

stores/modalControl.js

import { defineStore } from "pinia";

export default defineStore("modalControl", {
  state: () => ({ name: "胖胖", isFirstOpen: false, isSecondOpen: false })
});

app.vue 中的設定

  • 把兩個元件 import 進來,當成子元件。

  • 把 pinia 中的狀態及方法匯入。
    import { storeToRefs } from "pinia"
    import modalControl from "@/stores/modalControl.js";

  • 設定開啟及切換 modal 的方法

const openModal = function () {
      isFirstOpen.value = true;
      isSecondOpen.value = false;
    };
    // 開啟第二個 modal,關閉第一個 modal
    const openSecond = function () {
      isSecondOpen.value = true;
      isFirstOpen.value = false;
    };
  • 將兩個按鈕綁上開啟 modal 的方法。
<button type="button" class="btn" @click="openModal">showModal</button>
<button type="button" class="btn" @click="openSecond">showModal2</button>

app.vue

<template>
  <div class="up">
    <button type="button" class="btn" @click="openModal">showModal</button>
    <button type="button" class="btn" @click="openSecond">showModal2</button>
  </div>

  <!-- <Modal :open="isOpen" @close="isOpen = !isOpen"> -->
  <Modal>
    <p>
      {{ name }}
      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quo molestiae
      consectetur blanditiis aspernatur velit autem labore amet
    </p>
  </Modal>
  <SecondModal>
    <p>
      {{ name }}
      Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quo molestiae
      consectetur blanditiis aspernatur velit autem labore amet
    </p>
  </SecondModal>
</template>

<script>
import { ref } from "vue";
// 第一個元件
import Modal from "./components/ModalView.vue";
// 第二個元件
import SecondModal from "./components/SecondModal.vue";
// pinia 中共用的狀態
import modalControl from "@/stores/modalControl.js";
// 使用 pinia 中資料的方法
import { storeToRefs } from "pinia";
// import HelloWorldVue from "./components/HelloWorld.vue";
export default {
  components: { Modal, SecondModal },
  setup() {
    // 把 pinia 中元件共用的狀態,賦值給 modaltest 這個變數
    const modaltest = modalControl();
    // 解構
    const { name, isFirstOpen, isSecondOpen } = storeToRefs(modaltest);
    // const isOpen = ref(false);
    // 開啟第一個 modal,關閉第二個 modal
    const openModal = function () {
      isFirstOpen.value = true;
      isSecondOpen.value = false;
    };
    // 開啟第二個 modal,關閉第一個 modal
    const openSecond = function () {
      isSecondOpen.value = true;
      isFirstOpen.value = false;
    };
    return {
      isFirstOpen,
      modalControl,
      name,
      isSecondOpen,
      openModal,
      openSecond,
    };
  },
};
</script>

<style>
.up {
  z-index: 500;
  cursor: pointer;
}
</style>

2 個 modal 元件的設定

ModalView.vue

<template>
  <transition name="fade">
    <div class="vue-modal" v-if="isFirstOpen">
      <transition name="drop-in">
        <div class="vue-modal-inner" v-if="isFirstOpen">
          <div class="vue-modal-content">
            <slot />
            {{ name }} {{ isOpen }}
            <button type="button" @click="isFirstOpen = false">close</button>
          </div>
        </div>
      </transition>
    </div>
  </transition>
</template>

<script>
import { onMounted, onUnmounted } from "vue";
import modalControl from "@/stores/modalControl.js";
import { storeToRefs } from "pinia";
export default {
  setup() {
    const modaltest = modalControl();
    const { name, isFirstOpen } = storeToRefs(modaltest);
    return {
      modaltest,
      name,
      isFirstOpen,
    };
  },
};
</script>

SecondModal.vue

<template>
  <transition name="fade">
    <div class="vue-modal" v-if="isSecondOpen">
      <transition name="drop-in">
        <div class="vue-modal-inner" v-if="isSecondOpen">
          <div class="vue-modal-content">
            <slot />
            {{ name }} {{ isOpen }}
            <button type="button" @click="isSecondOpen = false">close</button>
          </div>
        </div>
      </transition>
    </div>
  </transition>
</template>

<script>
import modalControl from "@/stores/modalControl.js";
import { storeToRefs } from "pinia";
export default {
  setup(props, { emit }) {
    const modaltest = modalControl();
    const { name, isSecondOpen } = storeToRefs(modaltest);
    return {
      modaltest,
      name,
      isSecondOpen,
    };
  },
};
</script>

在 Composition API 中使用 pinia 操控 modal
https://popeye-ux.github.io/2023/01/29/modal-pinia/
作者
POPEYE
發布於
2023年1月29日
許可協議