pinia
# pinia
#
# 1. Pinia是什么?
Pinia 是 Vue.js 的新一代状态管理库,它是 Vuex 的替代品,提供了更简单、灵活和强大的状态管理功能。与 Vuex 类似,Pinia 也提供了 state
、getters
和 actions
来管理状态,但它在 API 设计和性能优化方面进行了改进。
Pinia 官方文档: Pinia Documentation (opens new window)
# 2. 搭建 Pinia 环境
要在 Vue 项目中使用 Pinia,需要先进行一些安装和配置步骤。
# 安装 Pinia
使用 npm 安装 Pinia:
npm install pinia
# 在 src/main.ts
中配置 Pinia
在 Vue 应用的入口文件中创建并使用 Pinia 实例。
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
/* 引入 createPinia,用于创建 Pinia 实例 */
import { createPinia } from 'pinia';
/* 创建 Pinia 实例 */
const pinia = createPinia();
/* 创建 Vue 应用实例 */
const app = createApp(App);
/* 注册 Pinia 插件到 Vue 应用 */
app.use(pinia);
/* 挂载应用 */
app.mount('#app');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置完成后,你可以在开发者工具中看到 Pinia
选项卡。
# 3. Pinia 的基础概念
Pinia 主要包括三个核心概念:state
、getters
和 actions
。这些概念分别对应于组件中的数据、计算属性和方法。
# 1. Store
概念:Store 是 Pinia 的核心,它是一个用于存储和管理应用状态的容器。每个 Store 都有一个唯一的标识符(
id
),并包含三个主要部分:state
、getters
和actions
。创建 Store:使用
defineStore
函数来定义和创建一个 Store。import { defineStore } from 'pinia'; // 定义一个名为 'main' 的 Store export const useMainStore = defineStore('main', { // state:存储状态的函数 state: () => ({ // 在 state 中存储应用的状态 }), // getters:计算属性的函数集合 getters: { // 在 getters 中定义计算属性 }, // actions:处理业务逻辑和异步操作的函数集合 actions: { // 在 actions 中定义业务逻辑和异步操作 } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. State
概念:
state
是 Store 中存储数据的地方,它类似于组件中的data
。所有的状态都定义在state
函数返回的对象中。语法和使用:
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { // state:返回一个对象,包含所有状态数据 state: () => ({ count: 0, // 定义一个 count 状态 user: { name: 'Alice', age: 25 } // 定义一个 user 对象 }) });
1
2
3
4
5
6
7
8
9在组件中使用
state
:在模板中可以直接使用
store.state
中的状态数据,在script
标签中通过 Store 实例来访问状态。<template> <div> <p>Count: {{ counterStore.count }}</p> <!-- 模板中直接使用 --> <p>User: {{ counterStore.user.name }} ({{ counterStore.user.age }} years old)</p> </div> </template> <script setup> import { useCounterStore } from '@/store/counter'; // 获取 Store 实例 const counterStore = useCounterStore(); // 在 script 标签中访问状态 console.log(counterStore.count); console.log(counterStore.user.name); </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3. Getters
概念:
getters
是 Store 中的计算属性,用于对状态进行派生计算或转换。它类似于组件中的computed
,但getters
可以依赖于其他getters
。语法和使用:
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), // getters:定义派生状态和计算属性 getters: { // 计算 count 的平方 squaredCount(state) { return state.count * state.count; } } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14在组件中使用
getters
:在模板中可以直接使用
store.getters
中的派生状态和计算属性,在script
标签中通过 Store 实例来访问getters
。<template> <div> <p>Squared Count: {{ counterStore.squaredCount }}</p> <!-- 模板中直接使用 --> </div> </template> <script setup> import { useCounterStore } from '@/store/counter'; // 获取 Store 实例 const counterStore = useCounterStore(); // 在 script 标签中访问 getters console.log(counterStore.squaredCount); </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4. Actions
概念:
actions
用于封装业务逻辑和处理异步操作。与getters
和state
不同,actions
既可以修改状态,也可以执行副作用。语法和使用:
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), // actions:定义用于处理业务逻辑和异步操作的函数 actions: { // 同步操作 increment() { this.count++; }, // 异步操作 async fetchCount() { const response = await fetch('/api/count'); const data = await response.json(); this.count = data.count; } } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20在组件中使用
actions
:在模板中可以绑定按钮点击事件来调用
actions
,在script
标签中通过 Store 实例来调用actions
。<template> <div> <p>Count: {{ counterStore.count }}</p> <button @click="counterStore.increment">Increment</button> <!-- 绑定按钮事件 --> <button @click="fetchCount">Fetch Count</button> </div> </template> <script setup> import { useCounterStore } from '@/store/counter'; // 获取 Store 实例 const counterStore = useCounterStore(); // 调用 actions 中的异步方法 async function fetchCount() { await counterStore.fetchCount(); } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4. 完整实例
以下是一个完整的实例,展示如何在 Vue.js 项目中使用 Pinia 来管理状态。
# 4.1 创建 Store
在 src/store
目录下创建一个 Store 文件 counter.ts
。
// src/store/counter.js
import { defineStore } from 'pinia';
// 定义并导出一个名为 'counter' 的 Store
export const useCounterStore = defineStore('counter', {
// state:存储状态
state: () => ({
count: 0 // 初始值
}),
// getters:计算属性
getters: {
// 获取 count 的平方
squaredCount(state) {
return state.count * state.count;
}
},
// actions:业务逻辑
actions: {
// 增加 count
increment() {
this.count++;
},
// 异步操作,模拟获取数据
async fetchCount() {
const response = await fetch('/api/count');
const data = await response.json();
this.count = data.count;
}
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 4.2 在组件中使用 Store
在组件中通过调用 Store 来获取和使用其中的状态、计算属性和方法。
<template>
<div>
<h2>当前计数:{{ counterStore.count }}</h2>
<p>平方:{{ counterStore.squaredCount }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="fetchCount">获取计数</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/store/counter';
// 获取 counterStore 实例
const counterStore = useCounterStore();
// 调用 actions 中的异步方法
async function fetchCount() {
await counterStore.fetchCount();
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 4. 修改数据(Pinia 中的三种方式)
在 Pinia 中,我们可以通过多种方式来修改存储在 Store 中的数据,包括直接修改、批量修改和通过 actions
修改。这三种方式各有优劣,适合不同的场景需求。
# 1. 直接修改
概念:直接修改是最简单的方式,可以直接赋值修改
state
中的属性。这种方式适用于简单的状态更新。示例:
// 假设我们有一个 countStore import { useCounterStore } from '@/store/counter'; // 获取 Store 实例 const counterStore = useCounterStore(); // 直接修改 state 中的 sum 属性 counterStore.sum = 666; // 直接赋值修改 sum
1
2
3
4
5
6
7
8优点:简单直接,适用于简单的数据更新。
缺点:不适合复杂的业务逻辑或需要批量更新的场景。
# 2. 批量修改
概念:使用
$patch
方法可以批量修改state
,同时更新多个属性。这种方式适用于需要同时更新多个属性的场景。语法:
// 使用 $patch 批量更新 state counterStore.$patch({ sum: 999, school: 'atguigu' });
1
2
3
4
5优点:方便进行批量更新,避免多次赋值。
缺点:不适用于包含复杂业务逻辑的场景。
# 3. 通过 Actions 修改
概念:通过
actions
可以封装业务逻辑,并在其中修改state
。这种方式适用于需要执行复杂逻辑或异步操作的场景。语法:
在 Store 中定义
actions
:import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ sum: 0 }), actions: { // 增加方法,带有业务逻辑 increment(value: number) { if (this.sum < 10) { this.sum += value; // 修改 sum 属性 } }, // 减少方法,带有业务逻辑 decrement(value: number) { if (this.sum > 1) { this.sum -= value; // 修改 sum 属性 } } } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21在组件中调用
actions
:在组件中,通过 Store 实例来调用定义的
actions
。<template> <div> <h2>当前求和为:{{ counterStore.sum }}</h2> <button @click="increment(2)">增加</button> <button @click="decrement(1)">减少</button> </div> </template> <script setup> import { useCounterStore } from '@/store/counter'; // 获取 counterStore 实例 const counterStore = useCounterStore(); // 定义方法来调用 actions function increment(value) { counterStore.increment(value); // 调用 increment 方法 } function decrement(value) { counterStore.decrement(value); // 调用 decrement 方法 } </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23优点:可以封装复杂逻辑,支持异步操作,易于测试和维护。
缺点:需要额外的代码编写,增加了一定的复杂性。
总结
- 直接修改:简单快捷,适用于简单的状态更新。
- 批量修改:使用
$patch
,适合同时更新多个属性。 - 通过 Actions 修改:封装复杂逻辑,支持异步操作,适用于复杂场景。
# 5. storeToRefs
storeToRefs
是 Pinia 提供的一个辅助函数,用于将 Store 中的状态和计算属性转换为 Vue 的 ref
对象。这样可以在模板中使用响应式数据时避免使用 .value
,并保持与其他 ref
数据一致的行为。
使用 storeToRefs
- 目的:将 Store 中的状态和计算属性转换为
ref
,以便在模板中更方便地使用。 - 注意:
storeToRefs
只会将 Store 中的状态和计算属性转换为ref
,而不会影响 Store 的结构或行为。 - 一旦使用
storeToRefs
转换为ref
,在模板中就不需要通过 Store 实例访问状态和计算属性。这提高了代码的可读性和维护性。
下面示例展示了如何在组件中使用 storeToRefs
将 Store 中的数据转换为 ref
,并在模板中使用这些 ref
数据。
<template>
<div class="count">
<!-- 在模板中直接使用 sum,无需使用 .value -->
<h2>当前求和为:{{ sum }}</h2>
</div>
</template>
<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count';
import { storeToRefs } from 'pinia';
// 获取 countStore 实例
const countStore = useCountStore();
// 使用 storeToRefs 转换 countStore 中的状态和计算属性
const { sum } = storeToRefs(countStore);
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
总结
- 便捷性:
storeToRefs
提供了一种便捷的方式,将 Store 中的数据转换为ref
,使得在模板中使用更加直观。 - 一致性:通过将 Store 的状态和计算属性转换为
ref
,可以在组件中保持对响应式数据的一致处理方式。 - 应用场景:特别适用于大型组件或应用中,减少模板中对
.value
的频繁使用,提高代码可读性。
# 6. $subscribe
Pinia 提供了 $subscribe()
方法用于侦听 Store 的状态变化。这允许你在状态变化时执行特定的逻辑,例如同步数据到 localStorage
或执行其他副作用。
语法:
store.$subscribe((mutation, state) => {
// mutation: 包含有关变更的信息
// state: 当前的状态
});
2
3
4
mutation
: 一个对象,包含有关变更的信息,通常包括变更类型和变更目标。state
: 当前 Store 的状态。
下面示例展示了如何使用 $subscribe
监听状态变化并将其存储在 localStorage
中。
// 在组件或其他合适的地方调用
talkStore.$subscribe((mutation, state) => {
console.log('状态变更:', mutation, state); // 打印变更信息和当前状态
// 将 talkList 存储到 localStorage 中
localStorage.setItem('talk', JSON.stringify(state.talkList));
});
2
3
4
5
6
- 监听变化:通过
$subscribe
可以监听 Store 的状态变化,以便在每次变化时执行自定义逻辑。 - 持久化数据:在状态变化时,可以将数据同步到
localStorage
或其他存储介质,以实现数据持久化。
# 7. Store 组合式写法
Pinia 支持组合式 API 语法来定义 Store,使得状态管理更加灵活和清晰。这种写法类似于 Vue 3 的组合式 API。
在 Pinia 中,使用组合式 API 定义 state
、getters
和 actions
提供了一种更灵活和模块化的方式来管理应用的状态。这种写法与 Vue 3 的组合式 API 一致,利用函数来定义和返回状态及行为。
# 组合式 API 基础语法
# 1. 定义 state
在组合式 API 中,使用 reactive
或 ref
定义 state
。
import { reactive, ref } from 'vue';
// 定义一个响应式对象作为状态
const state = reactive({
count: 0, // 基本数据类型
user: { name: 'Alice', age: 25 } // 对象类型
});
// 也可以使用 ref 定义单一状态
const count = ref(0);
2
3
4
5
6
7
8
9
10
# 2. 定义 getters
getters
是派生状态,用于计算和转换 state
中的数据。
import { computed } from 'vue';
// 定义一个计算属性
const squaredCount = computed(() => state.count * state.count);
// 如果使用 ref 作为状态,也可以这样定义 getters
const doubleCount = computed(() => count.value * 2);
2
3
4
5
6
7
# 3. 定义 actions
actions
是处理业务逻辑和异步操作的地方,可以直接修改 state
。
// 定义同步和异步的动作
function increment() {
state.count++;
}
async function fetchCount() {
const response = await fetch('/api/count');
const data = await response.json();
state.count = data.count;
}
2
3
4
5
6
7
8
9
10
# 使用组合式 API 定义 Store
可以使用 defineStore
函数结合上述语法来定义一个 Store。
import { defineStore } from 'pinia';
import { reactive, ref, computed } from 'vue';
// 使用 defineStore 定义 Store
export const useCounterStore = defineStore('counter', () => {
// 使用 reactive 定义 state
const state = reactive({
count: 0,
user: { name: 'Alice', age: 25 }
});
// 使用 computed 定义 getters
const squaredCount = computed(() => state.count * state.count);
// 定义 actions
function increment() {
state.count++;
}
async function fetchCount() {
const response = await fetch('/api/count');
const data = await response.json();
state.count = data.count;
}
// 返回 state、getters 和 actions
return {
state,
squaredCount,
increment,
fetchCount
};
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 在组件中使用 Store
<template>
<div>
<h2>当前计数:{{ count }}</h2>
<p>平方:{{ squaredCount }}</p>
<button @click="increment">增加</button>
<button @click="fetchCount">获取计数</button>
</div>
</template>
<script setup>
import { useCounterStore } from '@/store/counter';
// 获取 Store 实例
const counterStore = useCounterStore();
// 解构获取 state 和 actions
const { count, squaredCount, increment, fetchCount } = counterStore;
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
总结
- 组合式 API:使用
reactive
和ref
定义state
,使用computed
定义getters
,使用函数定义actions
。 - 模块化:将所有的状态、计算属性和业务逻辑放在一个函数中定义和返回,使得代码结构更加清晰和模块化。
- 灵活性:可以随意组合和调用状态、计算属性和动作,提高代码的可维护性和可读性。