我是靠谱客的博主 时尚面包,最近开发中收集的这篇文章主要介绍Vue状态管理--Vuex使用详解,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、状态管理概述

1.1 什么是状态管理

在组件中定义data或者在setup中返回使用的数据,这些数据称之为state

在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View

在模块中我们会产生一些行为事件,处理这些行为事件时,可能会修改state,这些行为事件称之为actions

他们三者之间总是相互影响的。

因此应用程序需要处理各种各样的数据,这些数据需 要保存在我们应用程序中的某一个位置。

对于这些数据的管理就称之为状态管理。其实就是一个数据变成另一个数据的过程。

1.2 不使用状态管理所带来的的问题

  • JavaScript开发的应用程序中:

    • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等
    • 也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页
    • 上面的状态数据会随着应用程序的变大越来越难以管理和维护
  • Vue3开发的应用程序中:

    • 当应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏

    • 来自不同视图的行为需要变更同一状态难以完成

    • 对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态

      但是对于复杂的状态管理来说,单纯通过传递和共享的方式是不足以解决问题的

1.3 Vuex的思想和作用

由于管理不断变化的state本身是非常困难的:

  • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化

  • 当程序复杂时,state在什么时候,因为什么原因而发生变化,发生怎么样的变化,会非常难以控制和追踪

因此,Vuex将组件的内部状态抽离出来,以一个全局单例的方式来管理这些状态:

  • 在这种模式下,组件树构成了一个巨大的 视图View

  • 不管在树的哪个位置,任何组件都能获取状态或者触发行为

  • 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性

这样代码边会变得更加结构 化和易于维护、跟踪。

Vue2.x时,它是主流的状态管理工具。现在依然很多项目再使用。

Vue3.x时,Vue官方也推荐使用Pinia进行状态管理。

二、Vuex的安装

npm run vuex

三、创建Store

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

  • store本质上是一个容器,它包含着你的应用中大部分的状态(state);

  • Vuex和单纯的全局对象的区别呢

    • Vuex的状态存储是响应式的
    • Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新
    • 不能直接改变store中的状态
      • 改变store中的状态的唯一途径就显示提交 (commit) mutation
      • 这样就可以方便的跟踪每一个状态的变化,让我们可以通过一些工具帮助我们更好的管理应用的状态
  • 语法演示:

    1)新建一个store文件夹,下面新建一个index.js

    import { createStore } from 'vuex'
    // createStore创建一个状态管理仓库
    const store = createStore({
    //仓库中定义着一些状态数据
    state: () => ({
    counter: 100,
    }),
    mutations: {
    increment(state) {
    state.counter++
    },
    })
    export default store
    

    2)main.js中注册

    import { createApp } from 'vue'
    import App from './App.vue'
    // 引入store目录下的index.js
    import store from './store'
    // use这个store,这样就可以通过$store去拿到状态管理仓库中的数据
    createApp(App).use(store).mount('#app')
    

    3)组件中获取状态管理仓库中数据

    <template>
    <div class="app">
    <!-- template中使用store中的counter -->
    <h2>App当前计数: {{ $store.state.counter }}</h2>
    </div>
    </template>
    <script setup>
    import {useStore} from 'vuex'
    // js代码中获取store中的counter
    const store = useStore();
    const conut = store.state.counter
    //调用mutations中的increment函数去修改counter
    function increment() {
    store.commit("increment")
    }
    </script>
    
  • 在组件中使用store,一般有如下几种情况

    • 在模板中使用

      <template>
      <div class="app">
      <!-- template中使用store中的counter -->
      <h2>App当前计数: {{ $store.state.counter }}</h2>
      </div>
      </template>
      
    • setup中使用

      <script setup>
      import {toRefs} from 'vue'
      import {useStore} from 'vuex'
      // js代码中获取store中的counter
      const store = useStore();
      // 这么赋值后数据conut不是响应式的,只是个普通js变量
      const conut = store.state.counter
      // 使用toRefs去解构,解构出来的数据还是响应式的
      const countRef = toRefs(store.state)
      //调用mutations中的increment函数去修改counter
      function increment() {
      store.commit("increment")
      }
      </script>
      
    • options api中使用,比如computed

      <script>
      export default {
      computed: {
      storeCounter() {
      return this.$store.state.counter
      }
      }
      }
      </script>
      

四、单一状态树

Vuex使用单一状态树: 即用一个对象就包含了全部的应用层级的状态。

这也意味着,每个应用将仅仅包含一个store实例。

单一状态树的优势:

  • 如果状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;

    所以Vuex也使用了单一状态树来管理应用层级的全部状态;

  • 单一状态树能够让我们最直接的方式找到某个状态的片段;

  • 而且在之后的维护和调试过程中,也可以非常方便的管理和维护;

五、获取多个状态数据–mapState的使用

如果有很多个状态都需要获取话,可以使用mapState的辅助函数:

  • mapState的方式一:数组类型;

    • 旧写法
    <script>
    import { mapState } from 'vuex'
    export default {
    computed: {
    ...mapState(["name", "level", "avatarURL"]),
    }
    }
    </script>
    
  • mapState的方式二:对象类型;

    • 旧写法
    <script>
    import { mapState } from 'vuex'
    export default {
    computed: {
    ...mapState({
    sName: state => state.name,
    sLevel: state => state.level
    })
    }
    }
    </script>
    
    • 主流写法

      mapStatesetup中并不好用,如果使用组合式API一般都按下面的方式写

    <script setup>
    import { toRefs } from 'vue'
    import { useStore } from 'vuex'
    // 直接对store.state进行解构(推荐)
    const store = useStore()
    const { name, level } = toRefs(store.state)
    function incrementLevel() {
    store.state.level++
    }
    </script>
    

从mapState的使用也可以看出来,Vuex适用的是选项式API,也就是适用于Vue2.x

而组合式API中用起来会很别扭,Vue3.x更加契合pinia

六、getters的使用

某些属性可能需要经过变化后来使用,这个时候可以使用getters

6.1 基本使用

getters可以传入两个参数:

  • state:可以用来获取到state中定义的数据
  • getters:可以直接获取其它的getters
import { createStore } from 'vuex'
// createStore创建一个状态管理仓库
const store = createStore({
//仓库中定义着一些状态数据
state: () => ({
counter: 100,
name: "张三",
level: 99,
avatarURL: "http://xxxxxx",
friends: [
{ id: 111, name: "why", age: 20 },
{ id: 112, name: "kobe", age: 30 },
{ id: 113, name: "james", age: 25 }
]
}),
getters: {
// getters基本使用
doubleCounter(state) {
return state.counter * 2
},
totalAge(state) {
return state.friends.reduce((preValue, item) => {
return preValue + item.age
}, 0)
},
// 2.在该getters属性中, 获取其他的getters
message(state, getters) {
return `name:${state.name} level:${state.level} friendTotalAge:${getters.totalAge}`
},
// 3.getters是可以返回一个函数的, 调用这个函数可以传入参数(了解)
getFriendById(state) {
return function(id) {
const friend = state.friends.find(item => item.id === id)
return friend
}
}
}
})
export default store
<template>
<div class="app">
<h2>doubleCounter: {{ $store.getters.doubleCounter }}</h2>
<h2>friendsTotalAge: {{ $store.getters.totalAge }}</h2>
<h2>message: {{ $store.getters.message }}</h2>
<!-- 根据id获取某一个朋友的信息 -->
<h2>id-111的朋友信息: {{ $store.getters.getFriendById(111) }}</h2>
<h2>id-112的朋友信息: {{ $store.getters.getFriendById(112) }}</h2>
</div>
</template>

其实这里也能看出来,这个getters和计算属性是非常相似的

6.2 Getters映射–mapGetters的使用

选项式API:

<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(["doubleCounter", "totalAge"]),
...mapGetters(["getFriendById"])
}
}
</script>

组合式API:

<script setup>
import { computed, toRefs } from 'vue';
import { mapGetters, useStore } from 'vuex'
const store = useStore()
// 1.使用mapGetters(较麻烦,不推荐)
// const { message: messageFn } = mapGetters(["message"])
// const message = computed(messageFn.bind({ $store: store }))
// 2.直接解构, 并且包裹成ref
// const { message } = toRefs(store.getters)
// 3.针对某一个getters属性使用computed(推荐写法)
const message = computed(() => store.getters.message)
function changeAge() {
store.state.name = "kobe"
}
</script>

七、Mutation的使用

更改Vuexstore中的状态的唯一方法是提交mutation

7.1 Mutation的基本使用

import { createStore } from 'vuex'
const store = createStore({
state: () => ({
counter: 100,
name: "张三",
level: 100
}),
// 定义可以被调用的mutations
mutations: {
increment(state) {
state.counter++
},
changeName(state, payload) {
state.name = payload
},
incrementLevel(state) {
state.level++
},
// newInfo是传递过来的参数
changeInfo(state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name
}
}
})
export default store
<template>
<div class="app">
<button @click="changeName">修改name</button>
<button @click="incrementLevel">递增level</button>
<button @click="changeInfo">修改info</button>
<h2>Store Name: {{ $store.state.name }}</h2>
<h2>Store Level: {{ $store.state.level }}</h2>
</div>
</template>
<script>
import { CHANGE_INFO } from "@/store/mutation_types"
export default {
methods: {
changeName() {
this.$store.commit("changeName", "王小波")
},
incrementLevel() {
this.$store.commit("incrementLevel")
},
changeInfo() {
this.$store.commit(changeInfo, {
name: "王二",
level: 200
})
}
}
}
</script>

7.2 Mutation定义常量类型

1)抽取常量到一个js文件中

export const CHANGE_INFO = "changeInfo"

2)使用常量

import { createStore } from 'vuex'
import { CHANGE_INFO } from './mutation_types'	//导入定义常量的js
const store = createStore({
state: () => ({
name: "张三",
level: 100
}),
// 定义可以被调用的mutations
mutations: {
// newInfo是传递过来的参数
changeInfo(state, newInfo) {
state.level = newInfo.level
state.name = newInfo.name
}
}
})
export default store
<script>
// 导入常量
import { CHANGE_INFO } from "@/store/mutation_types"
export default {
methods: {
changeInfo() {
// 使用常量
this.$store.commit(CHANGE_INFO, {
name: "王二",
level: 200
})
}
}
}
</script>

这也是Vue推荐的做法

7.3 mapMutations辅助函数

选项式API中:

<script>
import { mapMutations } from 'vuex'
import { CHANGE_INFO } from "@/store/mutation_types"
export default {
computed: {
},
methods: {
...mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
}
}
</script>

组合式API中:

<script setup>
import { mapMutations, useStore } from 'vuex'
import { CHANGE_INFO } from "@/store/mutation_types"
const store = useStore()
// 手动的映射和绑定
const mutations = mapMutations(["changeName", "incrementLevel", CHANGE_INFO])
const newMutations = {}
Object.keys(mutations).forEach(key => {
newMutations[key] = mutations[key].bind({ $store: store })
})
const { changeName, incrementLevel, changeInfo } = newMutations
</script>

Vuex确实不太适合在Vue3的组合式API中使用

7.4 mutation重要原则

mutation必须是同步函数 ,这意味着不可以在mutation方法中进行异步操作。

  • 因为devtool工具会记录mutation的日记;

  • 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;

  • 但是在mutation中执行异步操作,就无法追踪到数据的变化;

是如果我们希望在Vuex中发送异步·网络请求的话,可以使用actions

八、actions的使用

Action类似于mutation,不同在于:

  • Action提交的是mutation,而不是直接变更状态,也就是说,想要修改状态数据,必须经过mutation
  • Action可以包含任意异步操作;

8.1 基本使用

import { createStore } from 'vuex'
const store = createStore({
state: () => ({
// 模拟数据
counter: 100,
name: "张三",
level: 100,
}),
mutations: {
increment(state) {
state.counter++
},
},
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)
},
}
})
export default store
<template>
<div class="home">
<h2>当前计数: {{ $store.state.counter }}</h2>
<button @click="counterBtnClick">发起action修改counter</button>
<h2>name: {{ $store.state.name }}</h2>
<button @click="nameBtnClick">发起action修改name</button>
</div>
</template>
<script>
export default {
methods: {
counterBtnClick() {
// 调用一个action的话 需要使用dispatch方法
this.$store.dispatch("incrementAction")
},
nameBtnClick() {
// 向action中闯入参数的写法
this.$store.dispatch("changeNameAction", "aaa")
}
}
}
</script>

actions中的函数有一个非常重要的参数context

  • context是一个和store实例均有相同方法和属性的context对象;

  • context可以获取到commit方法来提交一个mutation

    也可以通context.statecontext.getters来获取stategetters

使用action时,进行action分发的方法:

  • 不带参: this.$store.dispatch("incrementAction")

  • 带参: this.$store.dispatch("changeNameAction", {count:100})

  • 直接传递对象:

    this.$store.dispatch({
    type: "increment",
    count: 100
    })
    

8.2 actions辅助函数–mapActions

  • 对象类型

    methods: {
    ...mapActions( ["increment" , "decrement"]),
    .. .mapActions({
    add : "increment" ,
    sub: "decrement"
    })
    }
    
  • 数组类型

    methods: {
    ...mapActions( ["increment" , "decrement"])
    }
    
  • 组合式API中的写法

    <script setup>
    import { useStore, mapActions } from 'vuex'
    const store = useStore()
    // 1.在setup中使用mapActions辅助函数
    // const actions = mapActions(["incrementAction", "changeNameAction"])
    // const newActions = {}
    // Object.keys(actions).forEach(key => {
    //
    newActions[key] = actions[key].bind({ $store: store })
    // })
    // const { incrementAction, changeNameAction } = newActions
    // 2.使用默认的做法
    function increment() {
    store.dispatch("incrementAction")
    }
    </script>
    

    组合式API中使用这个是比较复杂的

8.3 actions中的异步操作

import { createStore } from 'vuex'
const store = createStore({
state: () => ({
// 服务器数据
banners: [],
}),
mutations: {
changeBanners(state, banners) {
state.banners = banners
},
},
actions: {
fetchHomeMultidataAction(context) {
//方式一: Promise链式调用
/*
fetch("http://123.207.32.32:8000/home/multidata").then(res => {
return res.json()
}).then(data => {
console.log(data)
})
*/
// 方式二:
return new Promise(async (resolve, reject) => {
// 3.await/async
const res = await fetch("http://123.207.32.32:8000/home/multidata")
const data = await res.json()
// 修改state数据
context.commit("changeBanners", data.data.banner.list)
resolve("test")
}
}
}
},
modules: {
home: homeModule,
counter: counterModule
}
})
export default store
<template>
<div class="home">
<h2>Home Page</h2>
<ul>
<!-- 使用state中查询出来的数据 -->
<template v-for="item in $store.state.banners" :key="item.acm">
<li>{{ item.title }}</li>
</template>
</ul>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
// 告诉Vuex发起网络请求
const store = useStore()
store.dispatch("fetchHomeMultidataAction").then(res => {
console.log("home中的then被回调:", res)
})
</script>

九、module的使用

由于使用单一状态树,应用的所有状态会集中成一个比较大的对象。

当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。

每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块.

9.1 基本使用

1)新建一个module目录,下面新建一个home.js

home.vue组件中用到的状态数据写到这个js中去

export default {
state: () => ({
count: 0,
// 服务器数据
banners: []
}),
getters: {
doubleCount(state, getters, rootState) {
return state.count + rootState.rootCounter
}
},
mutations: {
incrementCount(state) {
console.log(state)
state.count++
}
changeBanners(state, banners) {
state.banners = banners
}
},
actions:
incrementCountAction(context) {
context.commit("incrementCount")
}
fetchHomeMultidataAction(context) {
return new Promise(async (resolve, reject) => {
// await/async
const res = await fetch("http://123.207.32.32:8000/home/multidata")
const data = await res.json()
// 修改state数据
context.commit("changeBanners", data.data.banner.list)
resolve("aaaaa")
})
}
}
}

2)store主文件中使用定义好的module

import { createStore } from 'vuex'
// 导入上面的js文件
import homeModule from './modules/home'
const store = createStore({
state: () => ({
rootCounter: 100,
}),
modules: {
home: homeModule,	//使用定义好的module
}
})
export default store

3)模板中使用

<template>
<div class="home">
<h2>Home Page</h2>
<!-- 1.使用state时, 是需要state.moduleName.xxx -->
<h2>Counter模块的counter: {{ $store.state.counter.count }}</h2>
<!-- 2.使用getters时, 是直接getters.xxx -->
<h2>Counter模块的doubleCounter: {{ $store.getters.doubleCount }}</h2>
<button @click="incrementCount">count模块+1</button>
</div>
</template>
<script setup>
import { useStore } from 'vuex'
// 告诉Vuex发起网络请求
const store = useStore()
// 派发事件时, 默认也是不需要跟模块名称
// 提交mutation时, 默认也是不需要跟模块名称
function incrementCount() {
store.dispatch("incrementCountAction")
}
</script>

使用模块中的状态数据,都不需要写模块名称。默认这些模块都会合并到主文件当中的。

9.2 module的命名空间

默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的。

这样使得多个模块能够对同一个 action 或 mutation 作出响应; Getter 同样也默认注册在全局命名空间。

但是这样可能会出现各种命名重复的问题。

如果希望模块具有更高的封装度和复用性,可以添加namespaced: true 的方式使其成为带命名空间的模块。

当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。

export default {
namespaced: true,
//加上这句话它就有了自己的命名空间
state: () => ({
count: 0,
// 服务器数据
banners: []
}),
}

使用时就需要加上模块名,也就是js文件的文件名:

<template>
<div class="home">
<h2>Counter模块的doubleCounter: {{ $store.getters["counter/doubleCount"] }}</h2>
<div>
</emplate>

9.3 module修改或派发根组件

就是模块中调用和修改根组件当中的东西。

//这里一共有六个参数
changeNameAction({commit,dispatch, state, rootState, getters, rootGetters}) {
commit( "changeName", "kobe");
// 重要的就是加上root: true即可
commit( "changeRootName", null, {root: true});
dispatch("changeRootNameAction", null, {root: true})
}

最后

以上就是时尚面包为你收集整理的Vue状态管理--Vuex使用详解的全部内容,希望文章能够帮你解决Vue状态管理--Vuex使用详解所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部