1.什么是Pinia
- Pinia是Vuex核心成员所开发出来的一款极其接近Vuex理念的轻量级状态管理库,也是Vuex@5版本的设计思想
- 它允许跨组件/页面共享状态
- Pinia 适用于 Vue 2 和 Vue 3,并且不需要你一定使用组合 API
2. 为什么使用Pinia
-
在使用Vuex时我们需要做一些事情:
- 修改state的值时,如果是同步更新需要通过Mutations,异步更新需要通过Actions
- 需要在Vuex的中使用Typescript时, 尤其是为state添加TypeScript,需要单独配置,但这并不容易, 因为目前Vuex对TypeScript的支持并不好,其中会遇到一系列的问题
- Vuex的模块化需要使用module
-
使用Pinia则不需要做以上处理和面对TypeScript的支持问题:
- Pinia并不是Vuex的替代品,而是另一种解决方案,他们的使用场景不同
- Pinia具有完整的TypeScript支持
- 体量小(体积约1kb)
- Store中的Actions既可以执行同步方法也可以执行异步方法
- 模块不需要嵌套,可以声明多个Store
- 支持Vue DevTools, SSR和webpack代码拆分
3. Vuex和Pinia的选择
- Pinia体积小,轻量,适用于中小型项目,项目大且复杂的选择Vuex
- Pinia中对state的修改不对同步或异步进行单独区分
- Pinia对TypeScript的支持比Vuex更好, 不需要单独配置,Vuex本身不支持TypeScript需要单独配置
4.安装Pinia
# npm
npm install pinia
# yarn
yarn add pinia
5. 在vue项目中引入Pinia(默认使用Vue3项目)
# vue3 + vite
import { createApp } from 'vue' // 引入vue
import App from './App.vue'
import { createPinia } from 'pinia' // 引入Pinia的创建函数
const app = createApp(App) // 创建Vue
app.use(createPinia()) // 向Vue中安装Pinia
app.mount('#app') // 挂载Vue到页面
# 如果您使用的是 Vue 2,您还需要安装一个插件并pinia在应用程序的根目录注入创建的插件
import Vue from 'vue'
import App from './App.vue'
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
pinia,
render: (h) => h(App)
})
6.Store
6.1 什么是Store
Store是一个实体,它持有未绑定到您的组件树的状态和业务逻辑。换句话说,它托管全局状态。它有点像一个始终存在并且每个人都可以读取和写入的组件。它包含三个概念,状态、getter和动作,并且可以安全地假设这些概念等同于
data,computed和methods在组件中 — Pinia官网
6.2 创建并访问Store
# Store/index.js
import { defineStore } from 'pinia' // 引入Pinia Store
export const useStore = defineStore('main', { // main 为标识id,是必须的参数,Pania 使用它来将Store连接到 devtools
// 声明state
state: () => ({
count: 0
})
})
# App.vue
<script setup>
import { useStore } from './store/index'
const store = useStore()
</script>
<template>
<div>{{ store.count }}</div>
</template>
7. State
7.1 声明state
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
// 声明state
state: () => ({
count: 0
})
// 或者
// state: () => {
// return { count: 0 }
//}
})
7.2 访问state
# 错误写法
<script setup>
import { useStore } from './store/index'
const { count } = store // 直接对数据进行解构,这是一种错误的写法,会破坏数据的响应式
</script>
<template>
<div>{{ count }}</div>
</template>
# 使用storeToRefs
<script setup>
import { useStore } from './store/index'
const { count } = storeToRefs(useStore())
// 可以使用storeToRefs从存储中提取属性同时保持其反应性
// 但是仅限于从中获取响应式的属性而且不对其进行操作,不包括actions中的函数
</script>
<template>
<div>{{ count }}</div>
</template>
8. 修改State
8.1 直接通过Store修改state
<script setup>
import { useStore } from './store/index'
const store = useStore()
console.log('===', store)
const add = () => {
store.count++
}
</script>
<template>
<div>count: {{ store.count }}</div>
<button @click="add">add</button>
<button @click="reset">add</button>
</template>
8.2 重置state
- 可以使用$reset,将数据恢复为初始状态
<script setup>
import { useStore } from './store/index'
const store = useStore()
console.log('===', store)
const add = () => {
store.count++
}
const reset = () => {
store.$reset()
}
</script>
<template>
<div>count: {{ store.count }}</div>
<button @click="add">add</button>
<button @click="reset">reset</button>
</template>
8.3 $patch修改state
- 可以通过$patch对state进行修改,允许对部分
state对象同时多个修改
# 声明state
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 1,
name: '张三'
})
})
# 修改state
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const store = useStore()
const { count } = storeToRefs(useStore())
console.log('count', count)
console.log('store.count', store.count)
const changeCount = () => {
store.$patch({
count: store.count + 10, // 这里使用store获取state的值
name: '李四'
})
}
</script>
使用store获取state的值的原因是因为:
- storeToRefs 返回的值其实是一个对象,参与计算会出现问题
- 通过store访问对应值仅仅是值本身
8.4 $patch修改基本数据类型的数组
# 定义state
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
items: ['1', '2']
})
})
# 修改数组
<script setup>
import { useStore } from './store/index'
const store = useStore()
const changeItems = () => {
store.$patch(state => {
state.items.push('3')
})
}
const changeItemsHead = () => { // 可以直接通过索引进行修改
store.$patch(state => {
state.items[0] = 20
})
}
</script>
<template>
<div v-for="(item, index) in store.items" :key="index">{{ item }}</div>
<button @click="changeItems">changeItems</button>
<button @click="changeItemsHead">changeItemsHead</button>
# 定义state
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
items: [
{ name: '张三', age: 20 },
{ name: '李四', age: 23 }
]
})
})
# 修改state
<script setup>
import { useStore } from './store/index'
const store = useStore()
const changeItems = () => {
store.$patch(state => {
state.items.push({ name: '王五', age: 30 })
})
}
</script>
<template>
<div v-for="(item, index) in store.items" :key="index">{{ item.name }} -- {{item.age}}</div>
<button @click="changeItems">changeItems</button>
</template>
- 但是以上的写法存在一个问题:通过patch对store中的state直接进行修改,会提高成本,甚至会在某些情况下出现问题,所以Pinia的
patch方法还接受一个函数来应对这种问题
# 只需要在修改后通知Pinia,已经进行了修改
<script setup>
import { useStore } from './store/index'
const store = useStore()
const changeItems = () => {
store.$patch(state => {
state.items.push({ name: '王五', age: 30 })
state.hasChanged = true
})
}
</script>
<template>
<div v-for="(item, index) in store.items" :key="index">{{ item.name }} -- {{item.age}}</div>
<button @click="changeItems">changeItems</button>
</template>
8.5 更换state
- 可以将store中的state整个进行替换
store.$state = { counter: 666, name: 'Paimon' }
- 也可以替换应用程序的Pinia实例上的state
pinia.state.value = {}
9. getters
- getters等同于Computed计算属性,getters可以接收state的值并进行计算处理
9.1 定义和获取getters
# 定义getters
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 1
}),
// 声明getters
getters: {
countDouble(state) {
return state.count * 2
}
}
})
# 使用getters
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { count, countDouble } = storeToRefs(useStore())
</script>
<template>
<div>count: {{ count }}</div>
<div>countDouble: {{ countDouble }}</div>
</template>
9.2 访问同一个store的其他getters
# 定义并使用其他getters
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 1
}),
// 声明getters
getters: {
countDouble(state) {
return state.count * 2
},
countDoublePlus() { // 使用其他getters
return this.countDouble + 1
}
}
})
# 访问getters
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { count, countDouble, countDoublePlus } = storeToRefs(useStore())
</script>
<template>
<div>count: {{ count }}</div>
<div>countDouble: {{ countDouble }}</div>
<div>countDoublePlus: {{ countDoublePlus }}</div>
</template>
9.3 getters传参
- getter可以通过返回函数的方式接受参数
# getters接受参数
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 1
}),
// 声明getters
getters: {
paramsCount(state) {
return (param) => {
return param + state.count
}
}
}
})
# 访问并传入参数
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { paramsCount } = storeToRefs(useStore())
</script>
<template>
<div>count: {{ paramsCount(10) }}</div>
</template>
9.4 访问其他store中的getters
# 定义一个store
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', {
// 声明state
state: () => ({
num: 1
}),
// 声明getters
getters: {
numDouble(state) {
return state.num * 6
}
}
})
# 在原来的store中使用另一个store的getters
import { defineStore } from 'pinia'
import { useMainStore } from './main'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 1
}),
// 声明getters
getters: {
getMain(state) {
const mainStore = useMainStore()
return state.count * 4 + mainStore.numDouble
}
}
})
# 访问getters
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { getMain } = storeToRefs(useStore())
</script>
<template>
<div>count: {{ getMain}}</div>
</template>
10. Actions
- actions是Pinia中定义处理逻辑函数的部分,可以获取到当前store整体
10.1 处理同步任务
# 定义state和actions
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 10
}),
// 声明actions
actions: {
changeCount(num) { // 这里可以接收参数
this.count += num
}
}
})
# 修改state的值
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { count } = storeToRefs(useStore())
const { changeCount } = useStore() // 这里actions的方法,不可以从storeToRefs中解构出来
</script>
<template>
<div>{{ count }}</div>
<button @click="changeCount(2)">changeCount</button>
</template>
10.1 处理异步任务
# 定义
import { defineStore } from 'pinia'
export const useStore = defineStore('index', {
// 声明state
state: () => ({
count: 10
}),
// 声明actions
actions: {
changeCount(num) {
setTimeout(() => {
this.count += num
}, 2000)
}
}
})
# 修改
<script setup>
import { useStore } from './store/index'
import { storeToRefs } from 'pinia'
const { count } = storeToRefs(useStore())
const { changeCount } = useStore()
</script>
<template>
<div>{{ count }}</div>
<button @click="changeCount(2)">changeCount</button>
</template>
- 使用async/await或Promise等方式
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
return error
}
},
}
})