vue状态管理 vuex / pinia
vue状态管理 vuex / pinia
Jin状态管理
在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是 状态管理
✅ vue2 / Options Api 建议使用
vuex
✅ vue3 / Composition Api 建议使用
pinia
Vuex
创建Store
每一个Vuex应用的核心就是 store(仓库)
vuex
和 单纯的全局对象区别:
- Vuex的状态存储是响应式的:
store
中的状态发生变化,那么读取store
数据相应的组件也会被更新 - 不能直接改变
store
的状态 : 改变store中的状态的唯一途径就显示提交(commit) mutation
安装 vuex install vuex
注意:Vue2 安装 Vuex3,Vue3 安装 Vuex4,版本需对应。
创建文件 src/store/index.js
const store = createStore({
state: () => ({
counter: 100,
}),
getters: { },
// 2.在该getters属性中, 获取其他的getters
mutations: {}
})
export default store
main.js
配置 store
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
在组件中访问使用 state 里的状态 $store.state.xxx
<tempate>
<div>$store.state.counter</div>
<tempate/>
vuex 五个核心概念
State
Vuex 管理的状态对象
在 /store/index.js
定义相关管理的状态数据
const store = createStore({
state: () => ({
counter: 100,
name: "jay",
level: 100,
avatarURL: "http://xxxxxx",
friends: [
{ id: 111, name: "aaa", age: 20 },
{ id: 112, name: "bbb", age: 30 },
{ id: 113, name: "ccc", age: 25 }
]
})
)}
在组件 vue 里 使用:$store.state.xxx
<template>
<div class="app">
<h2>当前计数: {{ $store.state.counter }}</h2>
</div>
</template>
<script>
export default {
computed: {
storeCounter() {
return this.$store.state.counter
}
}
}
</script>
mapState
如果我们有很多个状态都需要获取话,可以使用mapState的辅助函数
- mapState的方式一:对象类型
- mapState的方式二:数组类型
- 也可以使用展开运算符和来原有的computed混合在一起
<template>
<!-- 2.计算属性(映射状态: 数组语法) -->
<h2>name: {{ name() }}</h2>
<h2>level: {{ level() }}</h2>
<!-- 3.计算属性(映射状态: 对象语法) -->
<h2>name: {{ sName }}
<h2>level: {{ sLevel }}</h2>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 数组写法
...mapState(["name", "level", "avatarURL"]),
// 对象写法
...mapState({
sName: state => state.name,
sLevel: state => state.level
})
}
}
</script>
在setup
中使用mapState
- 通过useStore拿到store后去获取某个状态
默认情况下,Vuex并没有提供
setup
非常方便的使用 mapState 的方式,这里我们进行了一个函数的封装
import { useStore, mapState } from 'vuex'
import { computed } from 'vuex'
export function useState(mapper) {
const store = useStore()
const stateFns = mapState(mapper)
const state={}
Object.keys(stateFns).forEach(item=>{
// 改变 this ,绑定到 store
state[item] = computed(stateFns[item].bind({$store:store}))
})
return state
}
使用
import useState from "../hooks/useState"
// 使用useState
const { name, level } = useState(["name", "level"])
gettters
官方定义:Vuex 允许我们在 store 中定义
getter
(可以认为是 store 的计算属性)
Getter 接受 state 作为其第一个参数
// ...省略初始化配置
getters: {
// 1.基本使用
doubleCounter(state) {
return state.counter * 2
}
}
在组件中访问使用 getters 里的状态 $store.getters.xxx
<template>
<div class="app">
<h2>doubleCounter: {{ $store.getters.doubleCounter }}</h2>
</div>
</template>
使用 第二个参数getters
可以获取其他的 getters
getters: {
// 1.基本使用
doubleCounter(state) {
return state.counter * 2
},
message(state, getters) {
return `
name:${state.name}
level:${state.level}
doubleCounter:${getters.doubleCounter}`
},
}
getters 是可以返回一个函数的, 调用这个函数可以传入参数
getFriendById(state) {
return function(id) {
const friend = state.friends.find(item => item.id === id)
return friend
}
}
组件使用
<h2>id-111的朋友信息: {{ $store.getters.getFriendById(111) }}</h2>
mapGetters
mapGetters
辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doubleCounter',
'message',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.dbCount` 映射为 `this.$store.getters.doubleCounter`
dbCount: 'doubleCounter'
})
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交
mutation
// ...省略初始化配置
mutations: {
increment(state) {
state.counter++
},
// 提交mutation的时候,携带数据
changeName(state, payload) {
state.name = payload
},
incrementLevel(state) {
state.level++
}
}
组件使用
<button @click="changeName">修改name</button>
<button @click="incrementLevel">递level</button>
// ------
incrementLevel() {
this.$store.commit("incrementLevel")
},
changeName() {
this.$store.commit("changeName", "ddd")
},
对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type
属性的对象:
store.commit({
type: 'changeName',
name: ddd
})
❗重要的原则: 不要在mutation方法中执行异步操作
Mutation常量类型
定义常量:mutation-type.js
export const CHANGE_INFO = "changeInfo"
定义mutation
[CHANGE_INFO](state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name
}
提交mutation
$store.commit({
type: CHANGE_INFO
name: "ddd",
level: 200
})
mapMutations
借助于辅助函数,帮助我们快速映射到对应的方法中
...mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
与上面mapState
用法大致相同
Actions
Action类似于mutation,不同在于:
- Action提交的是mutation,而不是直接变更状态
- Action可以包含任意异步操作
参数context
context
是一个和 store 实例均有相同方法和属性的 context对象- 所以我们可以从其中获取到
commit
方法来提交一个mutation
,或者通过context.state
和context.getters
来获取state
和getters
定义
actions: {
incrementAction(context) {
// console.log(context.commit) // 用于提交mutation
// console.log(context.getters) // getters
// console.log(context.state) // state
context.commit("increment")
},
changeNameAction(context, payload) {
context.commit("changeName", payload)
},
}
使用
counterBtnClick(){
this.$store.dispatch("incrementAction")
}
// 携带参数
nameBtnClick() {
this.$store.dispatch("changeNameAction", "aaa")
}
// 对象形式
nameBtnClick(){
this.$store.dispatch({
type: 'changeNameAction',
name: 'ccc'
})
}
mapActions
action也有对应的辅助函数与上面大致相同
import { mapActions } from 'vuex'
export default {
methods: {
// 数组写法
...mapActions(['increment', 'incrementBy']),
// 对象写法
...mapActions({ add: 'increment' })
}
}
actions的异步操作
我们可以通过让 action 返回 Promise,在Promise 的 then 中来处理完成后的操作
actions: {
increment(context) {
return new Promise((resolve)=>{
setTimeout(()=>{
context.commit('increment')
resolve('异步完成')
},1000)
})
}
}
const store = useStore()
const increment = () =>{
store.dispatch('increment').then(res=>{
consele.log(res,'异步完成')
})
}
module
让代码更好维护,让多种数据分类更加明确,每一类数据及其相关操作对应一个
store
每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
同样,对于模块内部的 action,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
module的命名空间
- 默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的
- 这样使得多个模块能够对同一个 action 或 mutation 作出响应
- Getter 同样也默认注册在全局命名空间;
- 希望模块具有更高的封装度和复用性,可以添加
namespaced: true
的方式使其成为带命名空间的模块:- 当模块被注册后,它的所有
getter、action
及mutation
都会自动根据模块注册的路径调整命名
- 当模块被注册后,它的所有
在 action 中修改 root 中的 state
changeNameActtion({commit,dispattch,sttate,rootState,getters,rootGetters}){
commit('changeName','aaa')
commit('changeRootName',null,{root,true})
dispach('changeRootNameAction',null ,{root:true})
}
pinia
与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持 官网链接
安装
npm install pinia
创建一个pinia并且将其传递给应用程序
新建 /store/index.js
import {createPinia} from 'pinia'
const pinia = createPinia()
exportt default pinia
在 main.js
注册
import pinia from './store/index.js'
createApp(App).use(pinia).mount('#app')
Store
- 一个 Store (如 Pinia)是一个实体,它会持有为绑定到你组件树的状态和业务逻辑,也就是保存了全局的状态
- 它有点像始终存在,并且每个人都可以读取和写入的组件
- 你可以在你的应用程序中定义任意数量的 Store 来管理你的状态
Store有三个核心概念:
- state、getters、actions
- 等同于组件的 data、computed、methods
- 一旦 store 被实例化,你就可以直接在 store 上访问 state、getters 和 actions 中定义的任何属性
定义一个 Store
Store 是使用
defineStore()
定义的,需要一个唯一名称,作为第一个参数传递
import { defineStore } from 'pinia'
// 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useStore = defineStore('main', {
// 其他配置...
})
这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。
返回的函数统一使用
useXXX
作为命名方案,这是约定的规范
使用 Store
store 在它被使用之前是不会创建的,我们可以通过调用 use 函数来使用 Store
<template>
<div class="home">
<h2>count: {{ counterStore.count }}</h2>
</div>
</template>
<script setup>
import useCounter from '@/stores/counter';
const counterStore = useCounter()
}
}
如果对获取到的 Store 解构,那么会失去响应式,可使用
storeToRefs()
重新保持其响应性
State
state 是 store 的核心部分,因为store是用来帮助我们管理状态的。
定义:
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
export default useStore
操作 State
const store = useStore()
- 读取和写入:
store.count++
,通过 store 实例访问状态来直接读取和写入状态 - 重置 State:
store.$reset()
, 重置 到其初始值 - 改变 State:
store.$patch({ count: 100 })
,允许您使用部分 “state” 对象同时应用多个更改 - 替换 State:
store.$state = { count: 1 }
,设置为新对象来替换 Store 的整个状态
Getters
Getters相当于Store的计算属性,可以通过
defineStore()
中的getters
属性来定义它们
getters
中可以定义接受一个state
作为参数的函数
export const useStore = defineStore('main', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})
访问 Getters
访问当前 store 的 Getters
const store = useStore()
console.log(store.doubleCount)
Getters 中访问自己的其他 Getters
通过this来访问到当前store实例的所有其他属性
doublePlusOne: function(state) {
return this.doubleCount + 1
}
访问其他 store 的Getters
message: function(state){
const store = useStore()
return this.name + ':' store.nickname
}
Getters也可以返回一个函数,接受参数
const useCounter = defineStore("counter", {
state: () => ({
count: 99,
friends: [
{ id: 111, name: "aaa" },
{ id: 112, name: "bbb" },
{ id: 113, name: "ccc" },
]
}),
getters: {
//.getters也支持返回一个函数
getFriendById(state) {
return function(id) {
for (let i = 0; i < state.friends.length; i++) {
const friend = state.friends[i]
if (friend.id === id) {
return friend
}
}
}
},
}
})
使用
const counter = useCounter()
// ====
<p>{{ getFriendById(111) }}</p>
Actions
Actions 相当于组件中的 methods,使用 defineStore() 中的 actions 属性定义
getters一样,在 action 中可以通过 this 访问整个 store 实例的所有操作
定义
actions: {
increment() {
this.count++
},
incrementNum(num) {
this.count += num
}
}
使用
const counter = useCounter()
function increment() {
counter.increment()
}
Actions 中是支持异步操作的,并且我们可以编写异步函数,在函数中使用 await