在大型应用中,由于多个状态分散在许多组件中以及它们之间的交互,状态管理通常变得复杂。人们经常忽略 Vue 实例中的真实来源是原始数据对象。 - Vue 实例只是代理访问它。因此,如果你有一个状态需要由多个实例共享,则应避免重复,并按标识共享。
¥In large applications, state management often becomes complex due to multiple pieces of state scattered across many components and the interactions between them. It is often overlooked that the source of truth in Vue instances is the raw data object - a Vue instance simply proxies access to it. Therefore, if you have a piece of state that should be shared by multiple instances, you should avoid duplicating it and share it by identity.
如果希望组件共享状态,推荐使用 Pinia。在深入研究之前,请先查看它的 documentation。与 Vue 开发工具 浏览器扩展一起使用时,它具有像时间旅行调试这样的强大功能。
¥The recommended way to go if you want components sharing state is Pinia. Take a look at its documentation before diving in. It has a great feature when used along the Vue dev-tools browser extension like Time Travel debugging.
由于 Pinia 有非常详细的文档,我们不会详细介绍如何配置或使用。我们只会向你展示在 Quasar 项目中使用时文件夹结构的样子。
¥We won’t go into details on how to configure or use Pinia since it has great docs. Instead we’ll just show you what the folder structure looks like when using it on a Quasar project.
搭建 Quasar 项目文件夹时,你可以选择添加 Pinia。它将为你创建所有必要的配置。例如,创建 /src/stores
来处理你需要的所有 Pinia 相关代码。
¥When you scaffold a Quasar project folder you can choose to add Pinia. It will create all the necessary configuration for you. Like for example the creation of /src/stores
which handles all the Pinia related code that you need.
如果你在创建项目时未选择 Pinia 选项,但希望稍后添加它,那么你只需检查下一部分并创建 src/stores/index.js
文件(运行 quasar new store <name>
时会自动创建):
¥If you don’t choose the Pinia option during project creation but would like to add it later then all you need to do is to check the next section and create the src/stores/index.js
file (it’s automatically created when you run quasar new store <name>
):
import { defineStore } from '#q-app/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 defineStore((/* { ssrContext } */) => {
const pinia = createPinia()
// You can add Pinia plugins here
// pinia.use(SomePiniaPlugin)
return pinia
})
添加 Pinia 商店(Adding a Pinia store)
¥Adding a Pinia store
使用 Quasar CLI 通过 $ quasar new
命令添加 Pinia 存储非常简单。
¥Adding a Pinia store is easy with Quasar CLI through the $ quasar new
command.
$ quasar new store <store_name> [--format ts]
它将在 /src/stores
中创建一个文件夹,该文件夹由上述命令中的 “store_name” 命名。它将包含你需要的所有样板文件。
¥It will create a folder in /src/stores
named by “store_name” from the command above. It will contain all the boilerplate that you need.
假设你要创建一个 “counter” Pinia 商店。你发出 $ quasar new store counter
。然后,你会注意到新创建的 /src/stores/counter.js
文件:
¥Let’s say that you want to create a “counter” Pinia store. You issue $ quasar new store counter
. You then notice the newly created /src/stores/counter.js
file:
Pinia 存储示例:
¥Example of Pinia store:
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
actions: {
increment() {
this.counter++;
},
},
})
我们创建了新的 Pinia 商店,但尚未在我们的应用中使用。在 Vue 文件中:
¥We’ve created the new Pinia store, but we haven’t yet used it in our app. In a Vue file:
<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>
¥More info on defining a Pinia store.
访问 Pinia 商店中的路由(Accessing the router in Pinia stores)
¥Accessing the router in Pinia stores
只需在 Pinia 商店中使用 this.router
即可访问路由。
¥Simply use this.router
in Pinia stores to get access to the router.
以下是示例:
¥Here is an example:
import { defineStore } from 'pinia'
export const useWhateverStore = defineStore('whatever', {
// ...
actions: {
whateverAction () { // do NOT use arrow function
this.router.push('...')
}
}
}