為何捐款
API 探索器
升級指南
NEW!
quasar.config 檔案
將專案轉換為搭配 Vite 的 CLI
瀏覽器相容性
支援 TypeScript
目錄結構
命令列表
CSS 預處理器
路由
懶加載 - 代碼分割
處理資源
啟動檔案
預取功能
API 代理
處理 Vite
處理 process.env
使用 Pinia 進行狀態管理
使用 Vuex 進行狀態管理
Linter
測試 & 稽核
開發行動應用程式
Ajax 請求
開放開發伺服器給公眾
Quasar CLI with Vite - @quasar/app-vite
App Vuex Store

警告

Vue 團隊已棄用 Vuex,轉而支持 Pinia

在大型應用程式中,由於多個狀態分散在許多組件以及它們之間的互動,狀態管理通常變得複雜。經常被忽略的是,Vue 實例中的真理來源是原始資料物件 - Vue 實例只是代理對它的存取。因此,如果您有一個狀態片段應該由多個實例共享,您應該避免複製它,並通過身份共享它。

如果您希望組件共享狀態,建議的方式是 Vuex。在深入研究之前,請查看其文件。當與 Vue 開發者工具 瀏覽器擴充功能(如 Time Travel debugging)一起使用時,它有一個很棒的功能。

我們不會詳細介紹如何設定或使用 Vuex,因為它有很棒的文件。相反,我們只會向您展示在 Quasar 專案中使用它時的資料夾結構。

index.js
# Vuex Store 定義
<folder>
# Vuex Store 模組...
<folder>
# Vuex Store 模組...

預設情況下,如果您在創建 Quasar CLI 專案資料夾時選擇使用 Vuex,它將設定您使用 Vuex 模組。 /src/store 的每個子資料夾都代表一個 Vuex 模組。

如果您在專案建立期間沒有選擇 Vuex 選項,但稍後想要新增它,那麼您需要做的就是查看下一節並建立 src/store/index.js 檔案。

提示

如果 Vuex 模組對您的網站應用程式來說太過複雜,您可以變更 /src/store/index.js 並避免匯入任何模組。

新增 Vuex 模組。

透過 Quasar CLI 的 $ quasar new 指令,可以輕鬆新增 Vuex 模組。

$ quasar new store <store_name> [--format ts]

它將在 /src/store 中建立一個資料夾,名稱為上面指令中的 “store_name”。它將包含您需要的所有樣板程式碼。

假設您想要建立一個 “showcase” Vuex 模組。您執行 $ quasar new store showcase。然後您會注意到新建立的 /src/store/showcase 資料夾,其中包含以下檔案

index.js
# Vuex Store 定義
index.js
# 將模組組合在一起
actions.js
# 模組 actions
getters.js
# 模組 getters
mutations.js
# 模組 mutations
state.js
# 模組 state

我們已經建立了新的 Vuex 模組,但我們尚未告知 Vuex 使用它。因此,我們編輯 /src/store/index.js 並新增對它的參考

import { createStore } from 'vuex'
import showcase from './showcase'

export default function (/* { ssrContext } */) {
  const Store = createStore({
    modules: {
      showcase
    },

    // enable strict mode (adds overhead!)
    // for dev mode and --debug builds only
    strict: process.env.DEBUGGING
  })

  return Store
}

提示

如果您正在開發 SSR 應用程式,那麼您可以查看 ssrContext 物件,該物件在伺服器端提供。

現在我們可以在我們的 Vue 檔案中使用這個 Vuex 模組。這是一個快速範例。假設我們在 state 中設定了 drawerState 並新增了 updateDrawerState mutation。

/src/store/showcase/mutations.js

export const updateDrawerState = (state, opened) => {
  state.drawerState = opened
}

// src/store/showcase/state.js
// Always use a function to return state if you use SSR
export default function () {
  return {
    drawerState: true
  }
}

在 Vue 檔案中

<template>
  <div>
    <q-toggle v-model="drawerState" />
  </div>
</template>

<script>
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup () {
    const $store = useStore()

    const drawerState = computed({
      get: () => $store.state.showcase.drawerState,
      set: val => {
        $store.commit('showcase/updateDrawerState', val)
      }
    })

    return {
      drawerState
    }
  }
}
</script>

TypeScript 支援

如果您在創建 Quasar CLI 專案資料夾時選擇使用 Vuex 和 TypeScript,它將在 src/store/index.ts 中新增一些類型程式碼。若要在您的組件中取得類型化的 Vuex store,您需要像這樣修改您的 Vue 檔案

<template>
  <!-- ... -->
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { useStore } from 'src/store';

export default defineComponent({
  setup () {
    const $store = useStore();
    // You can use the $store, example: $store.state.someStoreModule.someData
  },
});
</script>

警告

使用 Vuex,目前只有 state 是強型別的。如果您想要使用類型化的 getters/mutations/actions,您將需要使用 Vuex 之上的額外套件或 Vuex 的替代品。

使用 Vuex Smart Module

完全類型化 store 的選項之一是名為 vuex-smart-module 的套件。您可以透過執行以下指令來新增此套件


$ yarn add vuex-smart-module

安裝完成後,您需要編輯 src/store/index.ts 檔案以使用此套件來建立 store。編輯您的 store 索引檔案以類似於以下內容

import { store } from 'quasar/wrappers';
import {
  createStore,
  Module,
  createComposable,
  Getters,
  Mutations,
} from 'vuex-smart-module';

class RootState {
  count = 1;
}

class RootGetters extends Getters<RootState> {
  get count () {
    return this.state.count;
  }

  multiply (multiplier: number) {
    return this.state.count * multiplier;
  }
}

class RootMutations extends Mutations<RootState> {
  add (payload: number) {
    this.state.count += payload;
  }
}

// This is the config of the root module
// You can define a root state/getters/mutations/actions here
// Or do everything in separate modules
const rootConfig = {
  state: RootState,
  getters: RootGetters,
  mutations: RootMutations,
  modules: {
    //
  },
};

export const root = new Module(rootConfig);

export default store(function (/* { ssrContext } */) {
  const rootStore = createStore(root, {
    strict: !!process.env.DEBUGGING,
    // plugins: []
    // and other options, normally passed to Vuex `createStore`
  });

  return rootStore;
});

export const useStore = createComposable(root);

您可以像使用普通 Vuex 一樣使用模組,並且在該模組中,您可以選擇將所有內容放在一個檔案中,或為 state、getters、mutations 和 actions 使用單獨的檔案。或者,當然,這兩者的組合。

只需在 src/store/index.ts 中匯入模組,並將其新增至您的 rootConfig。有關範例,請查看此處

在 Vue 檔案中使用類型化的 store 非常簡單,這是一個範例

<template>
  <q-page class="column items-center justify-center">
    <q-btn @click="store.mutations.add(3)" label="Add count" />
    <div>Count: {{ store.getters.count }}</div>
    <div>Multiply(5): {{ store.getters.multiply(5) }}</div>
  </q-page>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { useStore, root } from 'src/store';

export default defineComponent({
  name: 'PageIndex',
  setup() {
    const store = useStore();
    return { store };
  },
});
</script>

在 Boot 檔案中使用類型化的 store

在 Boot 檔案中使用 store 時,也可以使用類型化的 store。這是一個非常簡單的 Boot 檔案範例

import { boot } from 'quasar/wrappers'
import { root } from 'src/store'

export default boot(({ store }) => {
  root.context(store).mutations.add(5);
});

在 Prefetch 中使用類型化的 store

同樣地,當使用 Prefetch 功能時,您也可以使用類型化的 store。這是一個範例

<script lang="ts">
import { defineComponent } from 'vue';
import { root } from 'src/store';

export default defineComponent({
  name: 'PageIndex',
  preFetch ({ store }) {
    root.context(store).mutations.add(5);
  },
  setup () {
    //
  },
});
</script>

Store Code Splitting

您可以利用 PreFetch 功能來程式碼分割 Vuex 模組。

Code splitting Vuex Smart Module

與常規 Vuex 相比,使用 Vuex Smart Module 進行程式碼分割的工作方式略有不同。

假設我們有以下模組範例

/store/modules/index.ts

// simple module example, with everything in one file
import { Getters, Mutations, Actions, Module, createComposable } from 'vuex-smart-module';

class ModuleState { greeting = 'Hello'};

class ModuleGetters extends Getters<ModuleState> {
  get greeting () {
    return this.state.greeting;
  }
}

class ModuleMutations extends Mutations<ModuleState> {
  morning () {
    this.state.greeting = 'Good morning!';
  }
}

class ModuleActions extends Actions<ModuleState, ModuleGetters, ModuleMutations, ModuleActions> {
  waitForIt (payload: number) {
    return new Promise<void>(resolve => {
      setTimeout(() => {
        this.commit('morning');
        resolve();
      }, payload);
    })
  }
}

export const admin = new Module({
  state: ModuleState,
  getters: ModuleGetters,
  mutations: ModuleMutations,
  actions: ModuleActions,
});

export const useAdmin = createComposable(admin);

然後,我們希望僅在訪問特定路由組件時才載入此模組。我們可以使用(至少)兩種不同的方式來做到這一點。

第一種方法是使用 Quasar 提供的 PreFetch 功能,類似於常規 Vuex 的範例,可以在這裡找到。 為了做到這一點,我們在 router/routes.ts 檔案中定義了一個路由。 在這個範例中,我們有一個 /admin 路由,它是我們 MainLayout 的子路由

{ path: 'admin', component: () => import('pages/Admin.vue') }

我們的 Admin.vue 檔案看起來像這樣

<template>
  <q-page class="column items-center justify-center">
    {{ greeting }}
    <q-btn to="/" label="Home" />
  </q-page>
</template>

<script lang="ts">
import { defineComponent, onUnmounted } from 'vue';
import { registerModule, unregisterModule } from 'vuex-smart-module';
import { admin, useAdmin } from 'src/store/module';
import { useStore } from 'vuex';

export default defineComponent({
  name: 'PageIndex',
  preFetch ({ store }) {
    if (!store.hasModule('admin')) {
      registerModule(store, 'admin', 'admin/', admin);
    }
  },
  setup () {
    const $store = useStore()
    // eslint-disable-next-line
    if (!process.env.SERVER && !$store.hasModule('admin') && (window as any).__INITIAL_STATE__) {
      // This works both for SSR and SPA
      registerModule($store, ['admin'], 'admin/', admin, {
        preserveState: true,
      });
    }

    const adminStore = useAdmin();
    const greeting = adminStore.getters.greeting;

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-expect-error
    // eslint-disable-next-line
    if (module.hot) {
      module.hot.accept(['src/store/module'], () => {
        // This is necessary to prevent errors when this module is hot reloaded
        unregisterModule($store, admin);
        registerModule($store, ['admin'], 'admin/', admin, {
          preserveState: true,
        });
      });
    }

    onUnmounted(() => {
      unregisterModule($store, admin);
    });

    return { greeting };
  },
});
</script>

第二種方法是使用 router.beforeEach 鉤子來註冊/取消註冊我們的動態 store 模組。 如果你的應用程式中有一個部分只有少數訪客會使用,那麼這就很有意義。 例如,你的網站的 /admin 部分,你在其下有多個子路由。 然後你可以檢查路由是否以 /admin 開頭,並根據這一點為每個以 /admin/... 開頭的路由載入 store 模組。

要做到這一點,你可以在 Quasar 中使用一個 Boot File,看起來像這樣

提示

下面的範例旨在與 SSR 和 SPA 一起使用。 如果你只使用 SPA,則可以通過完全刪除 registerModule 的最後一個參數來簡化此操作。

import { boot } from 'quasar/wrappers';
import { admin } from 'src/store/module';
import { registerModule, unregisterModule } from 'vuex-smart-module';

// If you have never run your app in SSR mode, the ssrContext parameter will be untyped,
// Either remove the argument or run the project in SSR mode once to generate the SSR store flag
export default boot(({ store, router, ssrContext }) => {
  router.beforeEach((to, from, next) => {
    if (to.fullPath.startsWith('/admin')) {
      if (!store.hasModule('admin')) {
        registerModule(store, ['admin'], 'admin/', admin, {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-expect-error
          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
          preserveState: !ssrContext && !from.matched.length && Boolean(window.__INITIAL_STATE__),
        })
      }
    } else if (store.hasModule('admin')) {
      unregisterModule(store, admin);
    }

    next();
  });
});

在你的元件中,你可以直接使用動態模組,而無需擔心註冊它。 例如

<template>
  <q-page class="column items-center justify-center">
    {{ greeting }}
    <q-btn to="/" label="Home" />
  </q-page>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { useAdmin } from 'src/store/module';

export default defineComponent({
  name: 'PageIndex',
  setup() {
    const adminStore = useAdmin();
    const greeting = adminStore.getters.greeting;

    return { greeting };
  }
});
</script>

在 Vuex stores 中存取 router

只需在 Vuex stores 中使用 this.$router 即可存取 router。

這是一個範例

export function whateverAction (state) {
  this.$router.push('...')
}