状态管理库(Vuex&Pinia)
Vuex
概述
vuex是事项组件全局状态(数据)管理的一种机制,可以方便实现组件之间数据的共享
好处:
1、集中管理共享的数据,抑郁开发和后期维护
2、高效的实现组件之间的数据共享,提高开发效率
3、存储的数据都是响应式的,能够实时保持数据与页面的同步
存储数据类型:组件之间共享的数据才有必要存储到vuex中,对于组件中的私有数据,依旧存储在自身的data中即可
安装
配置vue项目
安装依赖包:
npm install vuex --save
导入vuex包:
import Vuex from 'vuex'
Vue.use(vuex)
创建store对象:
const store=new Vuex.Store({
//state中存放的就是全局共享的数据
state:{count:0}
})
挂载store对象到vue实例中
new Vue({
el:'#app',
render:h=>h(app),
router
//将创建的共享数据对象,挂载到vue实例中
//所有的组件直接从store中获取全局的数据
store
})
基本语法
基本操作
大致框架:
store/index.js
import {createStore}from 'vuex'
export default createStore({
state:{
//数据
},
getters:{
//普通方法,要有return
//注:getters只是基于 state 的派生状态,不会改变原始的 state 数据,只是对数据进行加工处理。
},
mutations:{
//计算方法,其中的方法形参可以为(context,payload),context表示上下文对象包括commit,payload表示调用这个函数的时候给的参数
//调用mutations中的函数需要state.commit('函数名',payload(调用这个函数时传回的参数))
},
actions:{
//用来包装mutations中函数的异步操作,直接在异步操作中调用context.commit('函数名',payload)
},
modules:{
}
})
App.vue
<template>
<div>
<span>{{$store.state.属性名}}</span>
<p>{{$store.getters.方法名}}</p>
<p>{{$store.getters['方法名']}}</p>
<button @click="mutaionsFn">mutations</button>
<button @click="actionFn">actions</button>
</template>
<script>
import {useStore}from 'vuex';
export default{
name:'App',
//记得要return方法
setup(){
const store=useStore();
const mutaionsFn=()=>{//调用mutations中的方法 store.commit('updateName')
};
//针对调用actions中的方法
const actionFn=()=>{
store.dispatch('increateAsync',6);
}
return{
mutaionsFn,actionFn
}
}
}
要点:
state/getter调用:$store.state/getter.属性名/方法名
getter调用还可以$store.getter['方法名']
mutations调用:store.commit('方法名',传回的参数名)//传回的参数可以为空
actions调用:
store.dispatch('方法名',传回的参数名)//传回的参数可以为空
模块
store/index.js
import {createStore}from 'vuex'
const moduleA={
state:{
//数据
},
getters:{
//getters方法(有return值)
},
};
const moduleB={
namespaced:true,
state:{
state:{
//数据
},
getters:{
//getters方法(有return值)
},
mutations:{
//计算方法
}
actions:{
//包装mutations中函数的异步操作
}
export defaule createStore({
modules: {
moduleA,
moduleB
}
})
}
要点:
如果声明namespaced:true,则表示开启了命名空间,调用方法的时候可以用访问模块名来调用(eg:$store.getters['moduleB/newName']),
如果未声明,则调用的时候要直接访问对应的方法名(eg:$store.getters.newName),相当于全局方法
一定要用modules:{模块名}来注册模块
App.vue
<template>
<div>
<p>{{ $store.state.模块名.属性名}}</p>
//没有namespaced:true时的方法访问方式
<p>{{ $store.getters.newName }}</p>
<p>{{ $store.state.moduleB.username }}</p>
//有namespaced:true时的方法访问方式
<p>{{ $store.getters['moduleB/newName'] }}</p>
<button @click="mutationsFn">mutationsFn</button>
<button @click="actionsFn">actionFn</button>
</div>
</template>
<script>
import { useStore } from 'vuex';
export default {
name:'App',
setup(){
const store=useStore();
const mutationsFn=()=>{
store.commit('moduleB/方法名')
};
const actionsFn=()=>{
store.dispatch('moduleB/方法名',传回参数);
};
return {
mutationsFn,actionsFn
};
}
}
</script>
要点:
没有namespaced:true时的方法访问方式:$store.getters.newName
有namespaced:true时的方法访问方式:$store.getters['模块名/方法名']
调用mutations的方法时,要用store.commit('模块名/方法名')
调用actions的方法时,要用store.dispatch('模块名/方法名')
数据持久化
配置:npm i vuex-persistedstate
模块文件user.js:
export defaule {
namespaced:true,
state(){
return{
profile:{
id:'',
avatar:'',//头像 nickname:'',//昵称
account:'',//账号名称
mobile:'',//收集号码 token:'',//token数据
}
}
},
mutations:{
setUser(state,payload){ state.profile=payload
}
}
}
store/index.js文件:
import {createStore}from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import user from 'user.js'
modules:{
user,
},
plugins:[
createPersistedState({
key:'shop-project-vuex-store',
paths:['user']
})
]
})
App.vue文件:
<template>
<div id="nav">
{{ $store.state.user.profile.account }}
<button @click="$store.commit('user/setUser',{account:'张三'})">更新用户账号名称</button>
</div>
</template>
<script>
import { useStore } from 'vuex';
export default {
name:'App',
}
</script>
要点:
配置文件:npm i vuex-persistedstate
store文件:
import {createStore}from 'vuex'
import createPersistedState from 'vuex-persistedstate'
export default createStore({
modules: {
//模块名
},
plugins:[
createPersistedState({
key:'shop-project-vuex-store',
paths:['需要持久化的模块名'],
})
]
})
请求拦截器&响应拦截器
axios配置
import axios from 'axios';
import store from '@/store'//导入store文件
import router from '@/router'//导入路由文件
const baseURL='https://apipc-xiaotuxian-front.itheima.net/'
//自定义axios实例
const instance=axios.create({
baseURL,//请求的基础URL
timeout:5000//请求超时时间,如果超过指定时间没有响应,则会报错超时
})
请求拦截器(在每个请求发送之前进行预处理操作)
// 第一个参数:实现具体的拦截任务,config表示请求的配置信息(包括URL、headers、data等)
instance.interceptors.request.use(config=>{
//获取user模块里面state返回的所有用户信息
const {profile}= store.state.user;
//检查用户信息是否能通过服务器校验
if(profile.token){
//将token字段添加到请求头中(这样就能在每次请求时自动带上用户的身份验证信息,确保请求能够通过服务器的身份验证。)
config.headers.Authorization=`Bearer ${profile.token}`;
}
//返回修改后的配置对象,这样 axios 才会继续执行这个请求。
return config;
//第二个参数:拦截失败后的操作
},err=>{
return Promise.reject(err)
})
响应拦截器
instance.interceptors.response.use(res=>{
return res.data
},err=>{
//token数据无效(没有通过服务器校验)
if(err.response&&err.response.status===401){
//清空无效的用户信息
store.commit('user/setUser',{});
//跳转到登录页面(获取全部地址,以防结构不同导致无法获取id),在组件当中是$route.fullPath,在js文件中需要router.currentRoute.value.fullPath来获取(相当于一个ref对象,要用value来访问他的值)
//如果用router.push(),则系统会自动解析url地址,如果地址中有诸如&的特殊符号,则只会读到这里,符号之后的字符读不到
const fullPsth=encodeURIComponent(router.currentRoute.value.fullPath)
//将所访问的当前路由信息传递到登录页面中,当用户登录成功以后直接跳转到以前所访问的页面
router.push('登录页面网址/reddirectUrl='+fullPath)//reddirectUrl是参数,用户登录以后就可以跳转到之前访问的URl地址
}
return Promise.reject(err)
})
请求函数封装
export default(url,method,submitData)=>{
return instance({
url,//请求的地址
method,//请求的方式(get,post,GET,POST)
[method.toLowerCase()==='get'?'params':'data']:submitData//判断method是get还是post(大写转为小写)然后将相应的参数传给服务端
请求测试
App.vue文件
<template>
<div id="nav">
<button @click="fn">测试请求</button>
</div>
</template>
<script>
import request from //请求拦截器文件
export default{
name:'App',
setup(){
const fn=()={
request('//请求地址',请求方法(get/post),{请求参数})
网址呈现:当前网址=%2F+之前访问的网址
Pinia
基本概念:Pinia是Vue的专属的最新的状态管理库,它允许跨越组件或页面共享状态,是Vuex状态管理工具的替代品
优势:
提供更简单的API(去掉了mutation)
提供更符合组合式风格的API(和Vue3新语法统一)
去掉了modules的概念,每一个store都是一个独立的模块
搭配了TypeScript一起使用提供可靠的类型推断
配置:
配置Vue项目文件
npm install pinia
main.js文件:
// 导入pinia
import { createPinia } from 'pinia'
//执行方法得到实例
const pinia=createPinia()
//将实例加入到app应用中
createApp(App).use(pinia).mount('#app')
定义Store
store文件:
//引入pinia文件
import {defineStore}from 'pinia'
//定义方法
import {ref}from 'vue'
export const useCounterStore=defineStore('counter',()=>{
const count=ref(0)
const increment=()=>{
count.value++
}
//getter定义
const doubleCount=computed(()=>count.value*2)
//定义异步action
const list=ref([])
const getList=async()=>{
const res=await axios.get(API_URL)
list.value=res.data.data.channels
}
// 要把所有的方法和数据全部return
return {count,doubleCount,increment,list,getList}
})
App.vue文件:
<template>
<button @click="counter.increment()">Count:{{ counter.count+1 }}</button>
{{ counter.doubleCount }}
<ul>
<li v-for="item in couter.list" :key="item.id">{{ item.label }}</li>
</ul>
</template>
<script setup>
import {onMounted}from 'vue'
import {storeToRefs} from 'pinia'
import {useCounterStore}from '@/stores/counter'
const counter=useCounterStore()
onMounted(()=>{
counter.getList()
})
// 直接解构赋值会让响应式丢失,所以要用storeToRefs包裹(保持响应式更新)
//storeToRefs只会对store的数据包裹,不会管方法
const {count.doubleCount}=storeToRefs(counter)
//方法直接从 原来的counter中解构赋值
const {increment}=counter
// //+1
// counter.count++
// //自动补全+1
// counter.$patch({count:counter.count+1 })
// //pinia方法+1
// counter.increment()
</script>
state
是 store 的数据 (data
),getters
是 store 的计算属性 (computed
),而 actions
则是方法 (methods
),可以接受同步/异步。