共计 6752 个字符,预计需要花费 17 分钟才能阅读完成。
Pinia
官网:https://pinia.vuejs.org/zh/
一个全新的用于 Vue 的状态管理库
介绍
pinia 最初是 一个实验,目的是在2019年11月左右重新设计 Vue 状态管理在 Composite API 上的样子,也就是下一代 Vuex
- 之前的 Vuex 主要服务于 Vue2,选项式 API
- 如果想要在 Vue3 中使用 Vuex,需要使用它的版本4
- 只是一个过渡的选择,还有很大缺陷
- 所以在 Vue3 伴随着组合式 API 诞生之后,也设计了全新的 Vuex:Pinia,也就是 Vuex5
提案链接:https://github.com/vuejs/rfcs/pull/271
- Vue2 和 Vue3 都支持
- 处理初始化安装和 SSR 配置之外,两者的 API 都是相同的
- 官方文档注意针对 Vue3 进行说明,必要的时候会提供 Vue2 的注释
- 支持 Vue DevTools
- 跟踪 actions、mutations 的时间线
- 在使用容器的组件中就可以观察到容器本身
- 支持 time-travel 更容易的调试功能
- 在 Vue2 中 Pinia 使用 Vuex 的现有接口,所以不能与 Vuex 一起使用
- 但是针对于 Vue3 中的调试工具支持还不够完美,比如还没有 time-travel 调试功能
- 模块热更新
- 无需重新加载页面即可修改您的容器
- 热更新的时候保持任何现有状态
- 支持使用插件扩展 Pinia 功能
- 相比 Vuex 有更好完美的 TypeScript 支持
- 支持服务端渲染
核心概念
Pinia 从使用者角度和之前 Vuex 几乎是一样的
Store(如 Pinia)是一个保存状态和业务逻辑的实体,它不绑定到您的组件树。换句话说,它承载全局 state。它有点像一个始终存在的组件,每个人都可以读取和写入。它有三个核心概念
在 Vuex 中有四个核心概念:
- State
- Getters
- Mutations
- Actions
state:类似组件的 data
,用来存储全局状态
{
todos: [
{ id: 1, title: '吃饭', done: false },
{ id: 2, title: '睡觉', done: false },
{ id: 3, title: '打豆豆', done: true }
]
}
getters:类似组件的 computed
,根据已有的 State 封装派生数据,也具有缓存的特性
doneCount() {
return todos.filter(item => item.done).length
}
actions:类似组件的 menthods
,用来封装业务逻辑,同步异步都可以
- 在 Vuex 中同步操作用 mutations,异步操作用 actions,太麻烦。Pinia 中没有 mutations
基本示例
首先创建一个 store:
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// 可以这样写
// state: () => ({ count: 0 })
actions: {
increment() {
// 在 Vuex 中需要搞两步:1. 定义 mutations 2. 提交 mutations
this.count++
}
}
})
在组件中使用它:
import { useCounterStore } from '@/stores/counter'
export default {
setuo() {
const counter = useCounterStore()
counter.count++
// with autocompletion
counter.$patch({ count: counter + 1 })
// or using an action instead
counter.increment()
}
}
甚至可以使用函数(类似于组件 setup()
)为更高级的用例定义 Store:
export const useCounterStore = defineStore('count', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
如果仍不熟悉 setup()
Composition API,Pania 也支持类似 Vuex 的一组 地图助手。可以以相同的方式定义商店,但随后使用 mapStores()
,mapActions()
:
export const useCounterStore = defineStore('count', () => {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
const useUserStore = defineStore('user', () => {
// ...
})
export default {
computed: {
// other computed properties
// ...
// gives access to this.counterStore and this.userStore
...mapStores(useCounterStore, useUserStore),
// gives read access to this.count and this.double
...mapState(useCounterStore, ['count', 'double'])
},
methods: {
// gives access to this.increment()
...mapActions(useCounterStore, ['increment'])
}
}
Pinia vs Vuex
Pinia 试图尽可能接近 Vuex 的理念。它只在测试 Vuex 下一次迭代的提案,并取得了成功,因为目前有一个针对 Vuex5 的开放式 RFC,其 API 与 Piniia 使用的 API 非常相似。Pinia 的作者(Eduardo)是 Vue.js 核心团队的一员,并积极参与 Router 和 Vuex 等 API 的设计。他对这个项目的意图是重新设计使用全局 Store 的体验,同时保存 Vue 平易近人的哲学。让 Pinia 的 API 与 Vuex 一样接近,因为它不断向前发展,使人们可以轻松地迁移到 Vuex,甚至在未来融合这两个项目(在 Vuex 下)。
关于版本问题:
- Vuex 当前最新版本是 4.x
- VueX4 用于 Vue3
- Vuex3 用于 Vue2
- Pinia 当前最新版本是 2.x
- 即支持 Vue2 也支持 Vue3
可以认为就是 Vuex5,因为它的作者是官方的开发人员,并且已被官方接管了
Pinia API 与 Vuex ≤4 有很大不同,即:
- 没有
mutations
,mutations 被认为是非常冗长的。最初带来了 devtools 集成,但这不再是问题 - 不在于模块的嵌套结构。仍可以通过在另一个 store 中导入和使用 store 来隐式嵌套 store,但 Pinia 通过设计提供扁平解构,同时仍然支持 store 之间的交叉组合方式。甚至可以拥有 store 的循环依赖关系
- 更好的
typescript
支持,无需创建自定义的复杂包装器来支持 TypeScript。所有内容都是类型化的,并且 API 的设计方式尽可能地利用 TS 类型推断 - 不再需要注入、导入函数、调用它们,享受自动补全
- 无需动态添加 stores,默认情况下它们都是动态的,您甚至不会注意到,仍可以随时使用 store 来注册它,但因为它是自动的,所以无需担心
- 没有命名空间模块,鉴于 store 的扁平架构,“命名空间” store 是其定义方式所固有的,您可以说所有 stores 都是命名空间的
关于名字
Pinia (发音为 /piːnjʌ/,类似英文中的 “peenya”) 是最接近有效包名 piña (西班牙语中的 pineapple,即“菠萝”) 的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。
关于作者
vuejs 核心团队成员 https://cn.vuejs.org/about/team.html
快速入门
安装
yarn add pinia
# or with npm
npm install pinia
如果应用程序使用 Vue2,还需要安装组合式 api 包:
@vue/composition-api
初始化配置
Vue3:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
如果你使用的是 Vue2,你还需要安装一个插件,并在应用的根部注入创建的 pinia:
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// 其他配置...
// ...
// 请注意,同一个`pinia'实例
// 可以在同一个页面的多个 Vue 应用中使用。
pinia,
})
这样才能提供 devtools 的支持。在 Vue 3 中,一些功能仍然不被支持,如 time traveling 和编辑,这是因为 vue-devtools 还没有相关的 API,但 devtools 也有很多针对 Vue 3 的专属功能,而且就开发者的体验来说,Vue 3 整体上要好得多。在 Vue 2 中,Pinia 使用的是 Vuex 的现有接口(因此不能与 Vuex 一起使用)。
定义和使用 Store
使用 Store
使用 storeToRefs 解构 Store 数据
状态
定义状态
访问 State
重置状态
与 Options API 一起使用
创建项目
yarn create vite
安装 Pinia
yarn add pinia
基本使用
1. 创建 pinia 示例并挂载
// src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
// 1.创建 Pinia 实例
const pinia = createPinia()
const app = createApp(App)
// 2.挂载到 Vue 根实例
app.use(pinia)
app.mount('#app')
2. 基本使用
// src/store/index.ts
import { defineStore } from 'pinia'
// 1.定义并导出容器
// 参数1:容器 ID,必须唯一,将来 Pinia 会把所有的容器挂载到根容器
// 参数2:选项对象
// 返回值:一个函数,调用得到容器实例
export const useMainStore = defineStore('main', {
/**
* 类似于组件的 data,用来存储全局状态的
* 1.必须是函数:是为了在服务端渲染的时候避免交叉请求导致的状态数据污染
* 2.必须是箭头函数:为了更好的 TS 类型推导
*/
state: () => {
return {
count: 100,
name: 'Joe',
arr: [1, 2, 3]
}
},
/**
* 类似于组件的 computed,用来封装计算属性,有缓存功能
*/
getters: {
// 函数接收一个可选参数: state 状态对象
count10(state) {
return state.count + 10
}
// 如果在 getters 中使用了 this 则必须手动指定返回值的类型,否则类型推导不出来
// count10():number {
// return this.count + 10
// }
},
/**
* 类似于组件的 methods,封装业务逻辑,修改 state
*/
actions: {
// 不能使用箭头函数定义 actions,箭头函数绑定外部 this
changeState() {
// this.count++
// this.name = 'qiaofugui' + this.count
// this.arr.push(4)
this.$patch((state) => {
state.count++
state.name = 'qiaofugui' + state.count
state.arr.push(4)
})
}
}
})
// 2.使用容器中的 state
// 3. 修改 state
// 4.容器中的 action 使用
组件使用
<template>
<p>{{ mainStore.count }}</p>
<p>{{ mainStore.name }}</p>
<p> {{ mainStore.arr }}</p>
<p> {{ mainStore.count10 }}</p>
<hr>
<p>{{ count }}</p>
<p>{{ name }}</p>
<p> {{ arr }}</p>
<hr>
<p>
<button @click="handelChangeState">修改数据</button>
</p>
</template>
<script lang='ts' setup>
import { storeToRefs } from 'pinia'
import { useMainStore } from '../store/index'
const mainStore = useMainStore()
console.log(mainStore)
// 这是有问题的,因为这样拿到的数据不是响应式的
// pinia 其实就是把 state 数据做了 reactive 处理了
// const { count, name, arr } = mainStore
// 解决办法就是使用 storeToRefs 把解构出来的数据做 ref 响应式代理
const { count, name, arr } = storeToRefs(mainStore)
// 修改数据
const handelChangeState = () => {
// 方式一:最简单的方式
// mainStore.count++
// mainStore.name = 'Joe'
// 方式二:如果需要修改多个数据,建议使用 $patch 批量更新
// mainStore.$patch({
// count: count.value + 1,
// name: 'qiaofugui' + count.value + 1,
// arr: [...mainStore.arr, 4]
// })
// 方式三:更好的批量更新方式 $patch 一个函数
// mainStore.$patch(state => {
// state.count++
// state.name = 'qiaofugui' + state.count
// state.arr.push(4)
// })
// 方式四:逻辑比较多的时候封装到 action 处理
mainStore.changeState()
}
</script>
<style lang="less" scoped></style>
Pinia 添加到 vue 项目中
- 使用 create-vue 创建空的新项目
npm init vue@latest
- 按照官方文档安装 pinia 到项目 https://pinia.vuejs.org/zh/getting-started.html
简单使用
getters
Pinia 中的 getters 直接使用 computed
函数进行模拟
import { ref, computed } from 'vue'
// 数据(state)
const count = ref(0)
// getter
const doubleCount = computed(() => count.value * 2)
action 实现异步
action 中实现异步和组件中定义数据、方法的风格完全一致
const API_URL = 'http://127.0.0.1/list'
// 准备数据(state)
const list = ref([])
// 异步 action
const loadList = async () => {
count { data: res } = await axios.get(API_URL)
list.value = res
}
storeToRefs
在组件中使用 storeToRefs
函数可以辅助保持数据(state + getter)的响应式解构
import { useCounterStore } from '@/stores/counter.js'
import { storeToRefs } from 'pinia'
// 保持数据响应式
const { count, doubleCount } = storeToRefs(counterStore)
属性使用 storeToRefs 函数进行解构,方法直接进行解构