vue状态管理 vuex / pinia

状态管理

在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是 状态管理

✅ vue2 / Options Api 建议使用 vuex

✅ vue3 / Composition Api 建议使用 pinia

Vuex

官网链接

创建Store

每一个Vuex应用的核心就是 store(仓库)

vuex 和 单纯的全局对象区别:

  1. Vuex的状态存储是响应式的:store中的状态发生变化,那么读取store数据相应的组件也会被更新
  2. 不能直接改变 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.statecontext.getters 来获取 stategetters

定义

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、actionmutation 都会自动根据模块注册的路径调整命名

在 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