Quasar CLI with Vite - @quasar/app-vite
使用Pinia进行状态管理

在大型应用中,由于多块状态散落在许多组件中以及它们之间的交互,状态管理往往变得复杂。人们常常忽略的是,Vue实例中的真实来源是原始数据对象–Vue实例只是代理了对它的访问。因此,如果你有一块应该被多个实例共享的状态,你应该避免重复它,而是通过身份来共享它。

如果你想让组件共享状态,推荐的方法是Pinia。在深入研究之前,请看一下它的文档。当与Vue dev-tools浏览器扩展一起使用时,它有一个很棒的功能,比如时间旅行调试。

我们不会详细介绍如何配置或使用Pinia,因为它有很棒的文档。相反,我们将向你展示在Quasar项目中使用它时,文件夹结构是什么样子的。

.
└── src/
    └── stores/       # Pinia
        ├── index.js  # Pinia initialization
        ├── <store>   # Pinia store...
        └── <store>   # Pinia store...

当你为Quasar项目文件夹搭建脚手架时,你可以选择添加Pinia。它将为你创建所有必要的配置。比如创建/src/stores,处理所有你需要的Pinia相关代码。

如果你在创建项目时没有选择Pinia选项,但想在以后添加它,那么你所需要做的就是检查下一节,并创建src/stores/index.[js|ts]文件 (当你运行quasar new store <name>时,它会自动创建):

// src/stores/index.js

import { store } from 'quasar/wrappers'
import { createPinia } from 'pinia'

/*
 * If not building with SSR mode, you can
 * directly export the Store instantiation;
 *
 * The function below can be async too; either use
 * async/await or return a Promise which resolves
 * with the Store instance.
 */

export default store((/* { ssrContext } */) => {
  const pinia = createPinia()

  // You can add Pinia plugins here
  // pinia.use(SomePiniaPlugin)

  return pinia
})

添加一个Pinia存储

通过Quasar CLI的"$ quasar new "命令,添加一个Pinia存储很容易。

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

它将在/src/stores中创建一个文件夹,以上述命令中的"store_name"命名。它将包含所有你需要的模板。

比方说,你想创建一个 "计数器 "Pinia商店。你发出$ quasar new store counter。然后你注意到新创建的/src/stores/counter.[js|ts]文件:

.
└── src/
    └── stores/
        ├── index.js     # Pinia initialization
        └── counter.js   # Pinia store

Pinia存储的例子:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
  actions: {
    increment() {
      this.counter++;
    },
  },
})

我们已经创建了新的Pinia存储,但我们还没有在我们的应用程序中使用它。在一个Vue文件中:

<template>
  <div>
    <!-- Option 1 -->
    <div>Direct store</div>
    <!-- Read the state value directly -->
    <div>{{ store.counter }}</div>
    <!-- Use getter directly -->
    <div>{{ store.doubleCount }}</div>

    <!-- Manipulate state directly -->
    <q-btn @click="store.counter--">-</q-btn>
    <!-- Use an action -->
    <q-btn @click="store.increment()">+</q-btn>
  </div>

  <div>
    <!-- Option 2 -->
    <div>Indirect store</div>
    <!-- Use the computed state -->
    <div>{{ count }}</div>
    <!-- Use the computed getter -->
    <div>{{ doubleCountValue }}</div>

    <!-- Use the exposed function -->
    <q-btn @click="decrementCount()">-</q-btn>
    <!-- Use the exposed function -->
    <q-btn @click="incrementCount()">+</q-btn>
  </div>

  <div>
    <!-- Option 3 -->
    <div>Destructured store</div>
    <!-- Use the destructured state -->
    <div>{{ counter }}</div>
    <!-- Use the destructured getter -->
    <div>{{ doubleCount }}</div>

    <!-- Manipulate state directly-->
    <q-btn @click="counter--">-</q-btn>
    <!-- Use an action -->
    <q-btn @click="increment()">+</q-btn>
  </div>
</template>

<script>
import { computed } from 'vue';
import { useCounterStore } from 'stores/counter';
import { storeToRefs } from 'pinia';

export default {
  setup() {
    const store = useCounterStore();

    // Option 2: use computed and functions to use the store
    const count = computed(() => store.counter);
    const doubleCountValue = computed(() => store.doubleCount);
    const incrementCount = () => store.increment(); // use action
    const decrementCount = () => store.counter--; // manipulate directly

    // Option 3: use destructuring to use the store in the template
    const { counter, doubleCount } = storeToRefs(store); // state and getters need "storeToRefs"
    const { increment } = store; // actions can be destructured directly

    return {
      // Option 1: return the store directly and couple it in the template
      store,

      // Option 2: use the store in functions and compute the state to use in the template
      count,
      doubleCountValue,
      incrementCount,
      decrementCount,

      // Option 3: pass the destructed state, getters and actions to the template
      counter,
      increment,
      doubleCount,
    };
  },
};
</script>

关于定义Pinia存储的更多信息.

在Pinia存储中访问路由器

只需在Pinia存储中使用this.router就可以访问路由器。

下面是一个例子:

import { defineStore } from 'pinia'

export const useWhateverStore = defineStore('whatever', {
  // ...
  actions: {
    whateverAction () {
      this.router.push('...')
    }
  }
}