一、为什么学习 Pinia
对于博主自己提出的问题,我认为作为开发人员,首先要有一颗谦卑的心,其次是不断的学习和探索,我们都喜欢和优秀的人共事,学习亦是如此。
1. Pinia 是什么
根据 Pinia官网给出的解释,Pinia
是Vue
的专属状态管理库
,注意这个专属
很有牌面,它允许你跨组件或页面共享状态。沒骗大家吧,跨组件
用它准好使。
2. 什么是 Store
我理解的Store更像是一个大仓库,它里面装着各种各样的杂物,对应到我们的 Store 就是三个状态state
、getter
、action
。
3. Pinia 优势
这里我提一嘴vuex
,其实 Vuex 同样是一个优秀的 Vue 状态管理库。而且 Pinia 是 Vuex 的迭代产品。那么为什么标题写的是 Pinia 而不是 Vuex 呢?只能说作者在工作中更倾向于使用 Pinia。趁着热乎劲,也就跟大家分享分享,仅此而已。
其实两者比较,给我最直观的感受有那么几点:
- 开发由
选项式
向组合式 API
风格迈进我在使用 Vuex 的时候大多以选项式为主,不符合我写 Vue3 的习惯(组合式 API),但是在 Pinia 中我可以快乐的使用组合式 API
。 - 极致的轻量化
Pinia
大小只有1kb
左右,体积可是相当的小,基本可以无视它的存在! - 代码编写更加合理(简化)如果使用的是
Vuex
,我们要考虑异步
的问题,如果我们的操作存在异步,我们就需要在action中进行,然后在mutation中变更状态(正常操作下),然而Pinia
的API
更加简单,它的 action 支持同步
和异步
。有着 Vue3 的组合式
API 风格,与Typescript
有着很好的配合,并且同时适配 Vue2 和 Vue3。 - 更好的模块管理有别于
Vuex
的module
,Pinia 让我们更方便的定义 Store,每一个 Store 都是独立的
,并且互不影响。值得注意的是 Vite 官网脚手架已经推荐使用 Pinia 啦。
二、Pinia 技能展示
本小节列举的案例都采用的是
选项式 API
,组合式 API 我不好给大家展示getters
,如果大家看我写的很水的话也可以移步到 Pinia 官网查看学习,如果大家要喷我,可不可以轻一点。另外再吐槽两句,这个Pinia名字起的是真好。
1. 安装 Pinia
- 使用 Pinia 的第一步就是先要安装它,这个我是在 Vue3 中进行使用的,找到我们的项目根目录,打开命令行工具,输入一下内容即可↓
// 如果 npm 慢大家可以使用 cnpm npm install pinia // 我这里下载的 pinia 版本是^2.0.36
- 找到项目的 main.[js/ts],引入我们的 Pinia 就可以愉快的玩耍喽!
import { createApp } from "vue"; import App from "./App.vue"; import { createPinia } from "pinia"; // 加上这一句 const pinia = createPinia(); // 还有这一句 const app = createApp(App); app.use(pinia); // 这一句 app.mount("#app");
- 创建我们自己的 store 刚才说的模块化就体现出来了,我们在 src 目录下新建一个 store 文件夹,里边是我们每一个模块,创建 store 需要用到 defineStore 方法,定义好就可以在我们的 Vue 中使用啦,接下来看勇宝代码演示↓
// src/store/user.ts import { defineStore } from 'pinia' // defineStore 的第一个参数是一个唯一标识(id),就跟我们每个人的身份证一样一样滴。 export const useUserStore = defineStore('users', { // 一些具体的配置属性,我们后面一一讲解 })
这里是我创建了一个 user 的 store,比如我们自己的网站,当用户登录成功后端返回给我们一些用户的个人信息,我们把它存到这个 store 里边。
2. state(状态初始化)
1. 初始化 state
比如我们的用户信息有username
(用户名),phone
(手机号),email
(邮箱),age
(年龄),那么我们就需要在 state 中初始化了↓
export const useUserStore = defineStore('users', { state: () => ({ username: 'mybj123', phone: '18888888888', email: 'mybj123@gmail.com', age: 28 }) })
就是这么简单几句话,我们就可以在组件中进行使用了。
2. 使用 state
我们来到首页,假如我们已经登录成功了,跳转到欢迎页面(src/views/home/index.vue)
<template> <div >尊贵的{{ store.username }},下午好</div> // 这样也是可以的 <div >尊贵的{{ store.$state.username }},下午好</div> </template> <script setup> import { useUserStore } from '@/store/user.ts' const userStore = useUsersStore(); </script>
看到了吗?就是这么的简单,我们可以直接对store.username
进行修改,如果觉得繁琐我们也可以进行解构。
另外注意:userStore.$state
也能拿到 store 的数据。
import { useUserStore } from '@/store/user.ts' const userStore = useUsersStore(); const { username } = userStore
3. 修改 state
这里需要注意了,通过上面的方式解构出来的变量会丢掉响应式。修改 username 的话,发现试图是不会变化的,大家可以自行敲一敲,体验一下,验证一下勇宝说的对不对。
不过呢,大家不用担心,pinia 已经给我们考虑到了。我们直接使用它的storeToRefs
。
import { storeToRefs } from 'pinia' import { useUserStore } from '@/store/user.ts' const userStore = useUsersStore(); // 注意这 const { username } = storeToRefs(userStore)
4. 重置 state
如果我们想把 state 数据还原要怎么操作呢?就比如我们修改用户信息的时候,写了一半觉得不好,突然想重置到之前的数据,重新修改怎么做呢?这里我们就要使用到$state
这个方法
<template> <button @click="reset">重置信息</button> </template> <script setup> ...... const reset = () => { userStore.$reset() } </script>
5. 批量修改 state
如果我们想同时修改用户名、手机号和邮箱,可以像下面这样,但是可能这种场景用得比较少。因为是赋值,所以大家懂的都懂了吧(我们需要把 state 里边的属性都赋值一个遍)
userStore.$state = { username: 'zhangsan', phone: '111111111', email: 'xxxx@outlook.com', age: 25 }
这里我们需要用到$patch
方法:
userStore.$patch({ username: 'zhangsan', phone: '111111111', email: 'xxxx@outlook.com' })
这个再提一嘴,$patch
还有一种写法,接收一个回调函数
,可以拿到state
:
userStore.$patch((state) => { state.username = 'zhangsan' })
3. getter(vue 版的计算属性)
1. 编写 getter
pinia 中的getter
属性和 vue 中的computed
基本一样,都需要有一个 return 返回值。
还是使用刚才的例子:
export const useUserStore = defineStore('users', { state: () => ({ username: 'mybj123', phone: '18888888888', email: 'mybj123@gmail.com', age: 28 }), getters: { // 过了一年涨一岁 addAge: (state) => state.age + 1; } })
2. 使用 getter
当我们在 store 中定义好 getter 后,我们就可以在组件中进行使用了。
<template> <button @click="updateAge">年龄涨一岁</button> {{ addAge }} </template> <script setup> import { storeToRefs } from 'pinia' import { useUserStore } from '@/store/user.ts' const userStore = useUsersStore(); const { age, addAge } = storeToRefs(userStore) const updateAge = () => { age.value ++ } </script>
当我们修改了 age 后我们的 addAge 也会进行变更。
当然了我们的 getter 之间是可以相互调用的,我们可以在我们的 getter 中使用this.age
就好了。
export const useUserStore = defineStore('users', { state: () => ({ username: 'mybj123', phone: '18888888888', email: 'mybj123@gmail.com', age: 28 }), getters: { // 过了一年涨一岁 addAge: (state) => state.age + 1; }, showInfo: function (state) { return `${ state.username }的年龄加 1 是: ${ this.addAge }` } })
这个地方值得我们去注意一下,很重要,大家可以明显的看到这里我没有使用箭头函数,这个里边涉及到 this 上下文的问题,挖可坑,以后来填。
3. getter 传递参数
这个给大家整活一下高级玩法,其实大家在工作中也都用到过,这个概念呢叫做函数柯里化。
// 比如我们写这个一个 getter,用户喜欢吃什么 getters: { loveFood: function (state) { return (food) => { return `${ state.username }喜歡吃${ food }` } } }
loveFood 的使用:
{{ loveFood('披薩')}} ...... const { loveFood } = storeToRefs(userStore)
4. action(业务逻辑层)
前面我们提到的 state 也好,还是 getter 也罢,终归它们都是属性(数据)
,就像我们 Vue 中的data
和computed
一个意思。如果我们想处理一下业务逻辑,比如我们要发送一个http 请求
,或者是通过一些复杂的逻辑判断来修改某些数据就会感觉getter
不妥啦! 这时候action
就派上用场了。
注意:actions 和 Vue 中的 methods 十分的相似,我们可以在 actions 中编写同步方法和异步方法。
1. 编写一个 action
export const useUserStore = defineStore('users', { state: () => ({ username: 'mybj123', phone: '18888888888', email: 'mybj123@mybj123.com', age: 28 }), getters: { // 过了一年涨一岁 addAge: (state) => state.age + 1; }, actions: { updateEmail(payload): { this.email = payload // 伪代码:我就不给大家写了 // 发送 http 请求,修改我们的邮箱为新值 } } })
这里我编写了一个修改用户邮箱的 action,payload
(名字随意起,自己能看懂就行)是我们传递的参数
。这里我只是简单给大家演示,我们完全可以在这里面写http 异步请求
,还有一点大家我们看到,我们 action 定义的方法中的this
是指向的当前store
。
2. 使用 action
<button @click="saveEmail">保存邮箱</button>
// store 引入 import { useUserStore } from '@/store/user.ts' // store 创建 const userStore = useUsersStore(); // 调用 const saveEmail = () => { userStore.updateEmail('zhangsan@qq.com') }
我们点击按钮后,调用 store 方法,完成邮箱的修改。
三、总结
这是我工作中使用到的一些技术点,可能不是那么的全面,如果大家之前有Vuex
的基础的话上手pinia
还是很简单的。所以还是希望大家在接触一个新的技术的时候呢?不要产生很大的畏惧感。回顾本文,其实这个 pinia 也就是那么三块:
state
、getters
、actions