Skip to content

一切要從觀察者模式開始說起。

觀察者模式

觀察者模式(Observer Design Pattern)是一種設計模式,它允許一個物件(被稱為主題或可觀察者)通知它的所有觀察者(或訂閱者)當它的狀態發生變化時。在我們的案例中,app 就是主題,而頁面元件就是觀察者。我們可以讓 app 維護一個觀察者列表,並且在 app 初始化完畢後通知所有的觀察者執行他們的回調函數。這樣,我們就可以保證頁面元件在 app 初始化完畢後才執行自己的初始化操作。

P.S. 觀察者模式和訂閱模式不同。

元件初始化範例

在 vue3 的應用程式中,如何在頁面元件掛載時等待 app 初始化的非同步資訊。這種情況可能發生在以下場景:我們的 app 需要在初始化時進行一些非同步的操作,例如取得驗證後,獲得的使用者 id 及名稱。然而,我們的頁面元件可能會用到這些資訊,例如取得這個使用者與該頁面相關的資料。如果我們直接在頁面元件的 created 或 mounted 鉤子中執行這些操作,可能會發生錯誤,因為 app 還沒有獲得這些資訊。因此,我們需要一種機制來保證頁面元件掛載時 app 已經初始化完畢,或者至少能夠在 app 初始化完畢後執行頁面元件的初始化操作。這就是觀察者模式可以幫助我們解決的問題。
範例:

app store

js
// app.js
import { defineStore } from 'pinia'

const loadingTags= []
const initProcedue = []
const afterInitCallBack = []
export const useAppStore = defineStore('app', {
  state: () => ({
    inited: false, // 表示 state 是否已經初始化完畢
  }),
  getters:{
    loading(){
      return loadingTags.length > 0
    }
  },
  actions: {
    // 添加 app 啟動程序,用來向觀察者列表中添加觀察者
    addInitProcedure(func) {
      initProcedue.push(func);
    },
    // 啟動 app
    async init() {
      initProcedue.forEach(async (x) => await x());
      // 將 initialized 設為 true,表示 app 已經初始化完畢
      this.inited = true;
      afterInitCallBack.forEach((x) => x());
    },
    // 添加啟動 app 之後要做的事
    onInited(func) {
      afterInitCallBack.push(func);
    },
    // 加入 loading tag
    AddLoading(key){
      loadingTags.push(key)
    },
    // 移除 loading tag
    RemoveLoading(key){
      const idx = loadingTags.indexOf(key)
      if(idx !== -1) loadingTags.splice(idx, 1)
    }
  }
})
// app.js
import { defineStore } from 'pinia'

const loadingTags= []
const initProcedue = []
const afterInitCallBack = []
export const useAppStore = defineStore('app', {
  state: () => ({
    inited: false, // 表示 state 是否已經初始化完畢
  }),
  getters:{
    loading(){
      return loadingTags.length > 0
    }
  },
  actions: {
    // 添加 app 啟動程序,用來向觀察者列表中添加觀察者
    addInitProcedure(func) {
      initProcedue.push(func);
    },
    // 啟動 app
    async init() {
      initProcedue.forEach(async (x) => await x());
      // 將 initialized 設為 true,表示 app 已經初始化完畢
      this.inited = true;
      afterInitCallBack.forEach((x) => x());
    },
    // 添加啟動 app 之後要做的事
    onInited(func) {
      afterInitCallBack.push(func);
    },
    // 加入 loading tag
    AddLoading(key){
      loadingTags.push(key)
    },
    // 移除 loading tag
    RemoveLoading(key){
      const idx = loadingTags.indexOf(key)
      if(idx !== -1) loadingTags.splice(idx, 1)
    }
  }
})

頁面元件

html
<script setup>
import { onMounted } from "vue";
import { useAppStore } from "store/app.js";
// import { useAuthStore } from "./store/auth";

const appstore = useAppStore();
// const authstore = useAuthStore();

appstore.addInitProcedure(() => {
  console.log("我會在 app 初始化後執行 1");
});
appstore.addInitProcedure(() => {
  console.log("我會在 app 初始化後執行 2");
});

onMounted(async () => {
  const loadingFlag = "app";
  appstore.AddLoading(loadingFlag);
  try {
    // authstore.Authentication(); // 取得驗證
    // authstore.RemoveKey(); // 移除 Key
    appstore.init(); // 初始化 app
  } catch (error) {
    // handle error
    console.error(error);
  } finally {
    appstore.RemoveLoading(loadingFlag);
  }
});
</script>
<script setup>
import { onMounted } from "vue";
import { useAppStore } from "store/app.js";
// import { useAuthStore } from "./store/auth";

const appstore = useAppStore();
// const authstore = useAuthStore();

appstore.addInitProcedure(() => {
  console.log("我會在 app 初始化後執行 1");
});
appstore.addInitProcedure(() => {
  console.log("我會在 app 初始化後執行 2");
});

onMounted(async () => {
  const loadingFlag = "app";
  appstore.AddLoading(loadingFlag);
  try {
    // authstore.Authentication(); // 取得驗證
    // authstore.RemoveKey(); // 移除 Key
    appstore.init(); // 初始化 app
  } catch (error) {
    // handle error
    console.error(error);
  } finally {
    appstore.RemoveLoading(loadingFlag);
  }
});
</script>