Pinia 学习
Pinia 最初是在 2019 年 11 月左右重新设计使用Composition API。从那时起,最初的原则仍然相同,但 Pinia 对 Vue 2 和 Vue 3 都有效,并且不需要您使用组合 API。
Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。
一、介绍
1.Pinia与Vuex的比较
Pinia
最初是为了探索Vuex
的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容;- 与
Vuex
相比,Pinia
提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API; - 最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。
1.1 不同点:
mutations
不再存在。他们经常被认为是 非常冗长。他们最初带来了 devtools 集成,但这不再是问题;- 更友好的
TypeScripts
支持,Vuex之前使用TS是很麻烦的; - 不再有
modules
的嵌套结构。您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系。 - 没有命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。
2. 简单使用 Pinia
2.1 安装
yarn add pinia
# 或者使用 npm
npm install pinia
2.2 文件内容
2.2.1 新建仓库文件夹
在src/store/index.js
:
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;
2.2.2 Vue使用pinia
在src/main.js
入口文件:
import { createApp } from 'vue';
import App from './App.vue';
// 引入pinia仓库
import pinia from './store';
// 使用pinia
createApp(App).use(pinia).mount('#app');
2.2.3 定义某个具体模块仓库
在此举例,定义用户仓库:在src/store/useUserStore.js
import { defineStore } from 'pinia';
// 定义用户仓库, defineStore()接收的第一个参数为该仓库唯一标识ID
// 第二个参数为定义的仓库内容
const useUserStore = defineStore('user', {
state() {
return {
username: 'fct',
age: 22
};
},
actions: {
ageIncrement() {
this.age++;
}
}
});
export default useUserStore;
2.2.4 在组件中使用
<template>
<h2>学习Pinia</h2>
<div class="block-area">
<h3>1.简单使用</h3>
<h3>用户名:{{ userStore.username }}</h3>
<h3>年龄:{{ userStore.age }}</h3>
<button @click="ageIncrement">年龄增加</button>
</div>
</template>
<script setup>
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
function ageIncrement() {
// 三种方法均可更新age
// userStore.age++;
// userStore.$patch({ age: userStore.age + 1 });
userStore.ageIncrement();
}
</script>
二、核心概念
1. 定义Store仓库
Store 是使用 defineStore()
定义的,并且它需要一个唯一名称,作为第一个参数传递,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools,将返回的函数可命名为 useXXX 。
import { defineStore } from 'pinia'
// useXXXStore 可以是 useUserStore、useCartStore 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useXXXXStore = defineStore('main', {
// other options...
})
1.1 使用
使用Store仓库,就是在组件内执行defineStore()
返回的函数。
一旦 store 被实例化,你就可以直接在 store 上访问 state
、getters
和 actions
中定义的任何属性。
store
是一个用reactive
包裹的对象,这意味着不需要在 getter 之后写.value
,但是,就像setup
中的props
一样,我们不能对其进行解构,否则将失去响应式。
<script setup>
import useUserStore from './store/useUserStore';
import { storeToRefs } from 'pinia';
import { computed } from 'vue';
// 实例化 store
const userStore = useUserStore();
// 解构后的值,将不是响应式的
const { username, age } = userStore;
// 使用计算属性将是响应式的
const usernameComputed = computed(() => userStore.username);
const ageComputed = computed(() => userStore.age);
// 使用 storeToRefs, 是响应式引用
const { username: usernameRef, age: ageRef } = storeToRefs(userStore);
</script>
2. State
默认情况下,您可以通过 store
实例访问状态来直接读取和写入状态。
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
// 读取
console.log(userStore.username, userStore.age);
// 写入
userStore.age++;
2.1 重置state
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
// 通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:
userStore.$reset();
2.2 同时修改多个state
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
// 除了直接用 userStore.age++ 修改 store,你还可以调用 $patch 方法。
// 它允许您使用部分“state”对象同时应用多个更改:
userStore.$patch({
age: userStore.age + 1,
username: '常青'
});
注意:
使用这种语法应用某些突变非常困难或代价高昂:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要您创建一个新集合。 正因为如此,
$patch
方法也接受一个函数来批量修改集合内部分对象的情况:
userStore.$patch((state) => { state.friends.push({ name: 'fct', age: 23 }); state.hasChanged = true; })
这里没有直接赋予
friends
一个新的数组,而只是在原来的数组中push(推送)了个新对象。
2.3 替换state
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
// 可以通过将其 $state 属性设置为新对象来替换 Store 的整个状态:
userStore.$state = { age: 666, username: 'Paimon' };
📌注意:
使用$state
,不是直接完全替换state,即不是采用为state直接赋我们给予的对象:
// ❌ 不是直接替换赋值
userStore.state = { age: 666, username: 'Paimon' };
而是采取合并策略,当我们设置的新对象里与原来state的值有重名情况,则覆盖(替换)掉原state中的数据,其他未重名数据则直接加入原state对象。
// 类似这样
Object.assign(userStore.state, { age: 666, username: 'Paimon' })
2.4 订阅状态的改变
可以通过 store 的 $subscribe()
方法查看状态及其变化,类似于 Vuex 的 subscribe 方法。 与常规的 watch()
相比,使用 $subscribe()
的优点是 subscriptions 只会在 patches 之后触发一次(例如,当使用上面的函数版本时)。
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
userStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// 与 userStore.$id 相同
mutation.storeId // 'user'
// 仅适用于 mutation.type === 'patch object'
mutation.payload
// state 即为状态
})
默认情况下,state subscriptions 绑定到添加它们的组件,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true }
作为第二个参数传递给 detach 当前组件的 state subscription:
// 此订阅将在组件卸载后保留
userStore.$subscribe((mutation, state) => {
}, { detached: true })
3. Getters
Getters类似于在组件中的 computed,getters依赖Store中state。
3.1 简单使用/访问其他getter
Getters接收“state状态”作为第一个参数, 大多数时候,getter 只会依赖state
状态,但是,他们可能需要使用其他 getter。我们可以使用this
来访问整个 store 的实例,从而访问其他getter。
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 自动将返回类型推断为数字
doubleCount(state) {
return state.counter * 2
},
// 在TypeScript中定义返回类型
// 使用this,返回类型必须明确设置
// doublePlusOne(): number {
doublePlusOne() {
return this.counter * 2 + 1
},
// 使用其他getter
doubleCountPlusOne() {
return this.doubleCount + 1
},
},
})
在组件中使用:
<template>
<p>Double count is {{ store.doubleCount }}</p>
<p>doublePlusOne is {{ store.doublePlusOne }}</p>
<p>doubleCountPlusOne is {{ store.doubleCountPlusOne }}</p>
</template>
<script setup>
import { useStore } from './store/useStore';
const store = useStore();
store.count = 3;
</script>
3.2 getter接收参数
一般使用getter都是直接返回具体的值,而当我们返回一个函数时可以接收任何参数:
import { defineStore } from 'pinia';
// 定义用户仓库
const useUserStore = defineStore('user', {
state() {
return {
username: 'fct',
age: 22,
friends: [
{ id: 111, name: 'lihua' },
{ id: 112, name: 'liulei' },
{ id: 113, name: 'wikiy' }
]
};
},
getters: {
getFriendById: (state) => {
return (id) => state.friends.find((friend) => friend.id === id);
}
}
});
export default useUserStore;
在组件中使用:
<template>
<div>
<h3>getter的使用</h3>
<p>朋友111: {{ userStore.getFriendById(111) }}</p>
<p>朋友113: {{ userStore.getFriendById(113) }}</p>
</div>
</template>
<script setup>
import useUserStore from './store/useUserStore';
const userStore = useUserStore();
</script>
3.3 访问其他 Store 的getter
使用其他store中的内容,可以直接引入store,然后实例化,就可以取出想要的数据。
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
4. Actions
Actions 相当于组件中的methods
, 非常适合定义业务逻辑。
4.1 简单使用
与 getters
一样,操作可以通过 this
访问 whole store instance 。actions
可以是异步的,您可以在其中await
任何 API 调用甚至其他操作。
import { defineStore } from 'pinia';
// 定义用户仓库
const useListStore = defineStore('list', {
state() {
return {
list: []
};
},
},
actions: {
async getAsyncData() {
const data = await fetch('url路径');
const res = await data.json()
this.list = res;
}
}
});
export default useListStore;
组件中使用:
<script setup>
import useListStore from './store/useListStore';
const listStore = useListStore();
// Actions 像 methods 一样被调用:
listStore.getAsyncData()
</script>
4.2 访问其他store
要使用另一个 store ,您可以直接在操作内部使用它,与getters
使用相同。
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: {}
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
// 访问其他store
const auth = useAuthStore()
// 使用 state
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
4.3 订阅Actions
可以使用 store.$onAction()
订阅 action 及其结果。接收一个回调函数作为参数。
after
处理 Promise 并允许您在 action 完成后执行函数。onError
允许您在处理中抛出错误。
const unsubscribe = someStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()
4.3.1 手动保留订阅
默认情况下,action subscriptions 绑定到添加它们的组件,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true
作为第二个参数传递给当前组件的 detach action subscription:
<script setup>
import useSomeStore from './store/useSomeStore';
const someStore = useSomeStore()
// 此订阅将在组件卸载后保留
someStore.$onAction(callback, true)
</script>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步