本系列教程目录:Vue3+Element Plus全套学习笔记-目录大纲
文章目录
第1章 Pinia
Pinia是Vue推出的一个状态管理库,Pinia用于替代Vuex并优化开发体验。Pinia 最初被设计为 Vuex 5 的提案,后来独立发展,成为官方推荐的状态管理工具。
Tips:Vuex 在Vue 3 时代不再积极更新,Pinia 则是更轻量的替代方案。
Pinia官网:
Pinia官网:https://pinia.vuejs.org/zh/
1.1 Pinia的概述
1.1.1 什么是状态管理
在使用Vue开发中,组件之间经常需要传值,基于父子、兄弟组件之间的传值可能会很方便,但是如果是没有关联的组件之间要使用同一组数据,这样就不得不在访问该组件的路径上携带这些参数,这样极为不便。
状态管理是指在应用程序中集中存储、管理和共享数据的机制,即建立一块区域存储所有组件共享的数据,类似于后端的session或者前端的localstorage。在前端开发中,状态(State)通常指:
- 1)组件内部的状态(如
data()
、ref
、reactive
定义的变量)。 - 2)跨组件的共享状态(如用户登录信息、全局主题、购物车数据)。
可以看到,状态(State)就是数据,状态管理就是放在一个集中的位置对这些数据进行统一管理,包括存储、访问、修改等。状态管理的核心就是解决数据在不同组件间的共享问题。
1.1.2 Pinia的核心概念
一个完整的Pinia包含三个部分,分别为Store、Getter、Actions,他们的概念如下:
- State:表示 Pinia Store 内部保存的数据(data)。
- Getter:可以认为是 Store 里面数据的计算属性(computed),用于获取最新的Store数据。
- Actions:是暴露修改数据的几种方式,用于修改Store数据。
1.1.3 搭建Pinia工程
和 vue-router 一样,我们想要使用 pinia 都需要先安装它。
首先创建一个Vite工程:
npm create vite
进入Vtie工程,安装vite依赖:
npm install
安装pinia组件:
npm install pinia # 按照pinia组件
修改mian.js,让Vue实例使用Pinia组件:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 导入 createPinia
import {createPinia} from 'pinia'
// 创建 pinia 实例
const pinia = createPinia()
createApp(App)
.use(pinia) // 使用 pinia 实例
.mount('#app')
1.2 Pinia - State
Store是存储数据的地方,我们也可以把它理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改。
1.2.1 State的基本使用
我们需要使用pinia提供的defineStore()方法来创建一个store,该store用来存放我们需要全局使用的数据。
首先在项目src目录下新建store文件夹,用来存放我们创建的各种store,然后在该目录下新建user.js文件,主要用来存放与user相关的store。
定义一个存储单元:
// 导入defineStore函数
import {defineStore} from 'pinia'
// 定义一个 user 存储单元(存储单元的名称不能与其他存储单元重名)
export const userStore = defineStore('user', {
// 定义state状态
state: () => {
return {
username: '小灰',
money: 10000
}
}
});
定义一个User组件,用于展示State的数据:
<script setup>
import {userStore} from '../store/user';
// 获取 store 对象
let user = userStore();
let {username, money} = user;
</script>
<template>
<div class="user">
<h3>This is user Page</h3>
<hr>
<p>Username: {{ username }}</p>
<hr>
<p>Money: {{ money }}</p>
</div>
</template>
<style scoped>
.user {
padding: 10px 30px;
border: 3px solid blue;
}
</style>
定义一个UserUpdate组件,用于修改State数据:
<script setup>
import {userStore} from '../store/user';
// 获取 store 对象
let user = userStore();
function changeMoney() {
user.money += 100;
console.log('新的余额:', user.money);
}
</script>
<template>
<div class="userUpdate">
<h3>This is userUpdate Page</h3>
<div>
<button @click="changeMoney">增加余额</button>
</div>
</div>
</template>
<style scoped>
.userUpdate {
padding: 10px 30px;
border: 3px solid green;
}
</style>
App.vue组件:
<script setup>
import User from "./components/user.vue";
import UserUpdate from "./components/userUpdate.vue";
</script>
<template>
<div class="app">
<h3>This is App Page</h3>
<user />
<user-update />
</div>
</template>
<style>
.app {
padding: 0 30px;
border: 3px solid red;
}
</style>
我们发现修改user中的money属性时,并不具备响应式功能:
想要提供响应式功能我们可以使用Pinia提供的storeToRefs函数(注意,此时ref函数无法提供响应式功能):
// 无法提供响应式功能
// let username = ref(user.username)
// let money = ref(user.money)
// 使用 Pinia 提供的 storeToRefs 转换为响应式对象
let {username, money} = storeToRefs(user);
1.2.3 批量更改 state 数据
使用 store 的$patch 方法我们可以对State中的数据进行批量修改。
在userUpdate组件中添加按钮:
<button @click="patchUser">批量修改</button>
提供函数:
function patchUser() {
user.$patch({
money: 20000,
username: '小黑'
});
}
但实际上,我们通过如下代码也可以对state中的数据进行批量修改:
user.username = '小黑';
user.money = 20000;
在 Pinia 中,$patch
和直接修改 store 的属性都能达到更新状态的目的,但两种方式的底层渲染区别不一样。
$patch
:一次性批量更新多个属性,只会触发一次订阅(如组件的重新渲染或 devtools 的更新)- 直接修改State:每次赋值都会触发独立的响应式更新。
user.username = '小黑'; // 触发一次更新
user.money = 20000; // 再触发一次更新
实际上,无论是通过$patch还是直接修改State都不推荐。Pinia官方推荐使用actions中的方法修改数据,这样无论是从性能上还是语义上都符合Pinia对修改数据的定义。
1.2.3 替换 State
有时我们想要替换整个State的数据可以使用$state方法。
在userUpdate组件中添加按钮:
<button @click="replace">替换整个State</button>
提供函数:
function replaceUser() {
// 替换整个State
user.$state = {username: '小白', money: 30000, age: 20};
// state中新增了一个age属性
console.log('新的state:', user.$state);
}
1.2.4 重置 State
有时候我们修改了 state 数据,想要将它还原,此时,我们直接调用 store 的$reset()方法即可将 state 数据还原。但需要注意的是,替换之后的 state 本质上更改了一个新的 state,此时如果使用重置那么将会重置回新的 state。
在userUpdate组件中添加按钮:
<button @click="reset">重置store</button>
提供函数:
// 重置store
function reset() {
user.$reset();
}
1.3 Pinia - Getter
getter属性值是一个对象,该对象里面是各种各样的方法。大家可以把getter想象成Vue中的计算属性,它的作用就是返回一个新的结果,就和computed一样。
1.3.1 Getter使用
定义Getter:
// 导入defineStore函数
import {defineStore} from 'pinia'
// 定义一个 user 存储单元(存储单元的名称不能与其他存储单元重名)
export const userStore = defineStore('user', {
// 定义state状态
state: () => {
return {
username: '小灰',
money: 10000
}
},
getters: {
// 调用getMoney方法,返回state.money+100
getMoney: (state) => {
return state.money + 100;
},
},
});
user.vue:
<script setup>
import {userStore} from '../store/user';
import {storeToRefs} from "pinia";
// 获取 store 对象
let user = userStore();
// let {username, money} = user;
// 无法提供响应式功能
// let username = ref(user.username)
// let money = ref(user.money)
// 使用 Pinia 提供的 storeToRefs 转换为响应式对象
let {username, money} = storeToRefs(user);
</script>
<template>
<div class="user">
<h3>This is user Page</h3>
<hr>
<p>Username: {{ username }} - - - - Money: {{ money }}</p>
<p>新余额:{{user.getMoney}}</p>
</div>
</template>
<style scoped>
.user {
padding: 0 30px;
border: 3px solid blue;
}
</style>
userUpdate.vue:
import {userStore} from '../store/user';
// 获取 store 对象
let user = userStore();
function changeMoney() {
user.money += 100;
console.log('使用State的money属性:', user.money);
console.log('使用Getter的getMoney方法:', user.getMoney);
}
1.3.2 Getter传参
getter函数就相当于store的计算属性,和vue的computed类似。computed是不能直接传递参数的,但我们可以通过一些办法来达到传值效果。
getter函数如果需要传值的话该函数内返回一个函数,这样在调用getter函数时相当于调用该函数内返回的那个函数。
定一个getAddMoney的Getter函数:
getAddMoney: (state) => {
// 调用addMoney方法,返回一个函数,参数为money,返回state.money+money
return (money) => {
return state.money + money;
}
}
在user.vue中使用Getter:
<p>新余额:{{user.getAddMoney(1000)}}</p>
在js中使用Getter:
console.log('使用Getter的getAddMoney方法:', user.getAddMoney(1000));
1.4 Pinia - Actions
state和getters属性都主要是数据层面的,并没有具体的业务逻辑代码,它们两个就和我们组件代码中的data数据和computed计算属性一样。那么,如果我们有业务代码的话,最好就是写在actions属性里面,actions就和我们组件代码中的methods相似,用来放置一些处理业务逻辑的方法。
actions方法用于处理业务逻辑,在实际场景中,该方法可以是任何逻辑,比如发送请求、存储token等等。我们可以把actions方法当作一个普通的方法即可,特殊之处在于该方法内部的this指向的是当前store。
定义一个actions:
// 导入defineStore函数
import {defineStore} from 'pinia'
// 定义一个 user 存储单元(存储单元的名称不能与其他存储单元重名)
export const userStore = defineStore('user', {
// 定义state状态
state: () => {
return {
username: '小灰',
money: 10000
}
},
getters: {
....
},
actions: {
pay(money) {
// this代表当前的userStore实例
this.money -= money;
console.log('支付了' + money + '元');
}
}
});
在userUpdate.vue中添加按钮:
<button @click="pay">购买商品</button>
添加函数:
function pay() {
// 购买商品
user.pay(500)
console.log('购买商品成功');
}