• Day43:VUEX


    1. 基本介绍

    这里介绍的VueX是匹配Vue3的V4版本,它绝大多数功能使用都和之前基本保持一致。

    1.1 官方定义

    先一起看一下官网对于VueX的定义:

    • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
    1.2 状态管理

    官网定义中提到了一个现代前端中非常重要的概念状态管理 ,怎么来理解这个概念呢?

    • 在Vue中我们会定义一些驱动应用的响应式数据,我们把这些数据叫做状态(state)
    • 定义的初始化数据会按照Vue的模板语法以声明式的方式映射到视图(view)
    • 用户在视图上的操作(actions)会引起状态变化从而导致视图刷新

    在复杂的应用中,我们会经常遇到以下的问题:

    • 多个视图依赖于同一状态。
    • 来自不同视图的操作需要变更同一状态。

    其实这些问题我们也能解决,但需要状态在组件之间反复传递,十分容易出现错误,开发和维护成本都非常高,我们为什么不把组件的共享状态抽取出来,放到一个单独的空间管理呢,这个空间可以被任意组件访问到,这其实就是VueX的设计思想

    1.3 VueX的必要性

    并不是所有的项目中都需要使用VueX,如果你不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果你的应用够简单,你最好不要使用 Vuex。但是如果你需要构建一个中大型应用,在组件外部管理状态往往是一个更好的选择,那么这个时候VueX便会是自然而然的选择了。

    1.4 项目安装

    通过npm命令在项目中安装VueX

    npm i vuex --save

    在package.json中确认正确的版本号

    1.5 基本使用

    每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

    1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

    安装好VueX后,在src目录下新建一个store文件夹,再建立一个index.js文件,我们在这个js文件中来创建我们的store容器。

    1. import { createStore } from 'vuex'
    2. // 创建一个新的 store 实例
    3. const store = createStore({
    4. state() {
    5. return {
    6. count: 0,
    7. }
    8. },
    9. mutations: {
    10. increment(state) {
    11. state.count++
    12. },
    13. },
    14. })
    15. export default store

    然后在入口文件main.js中引入store并使用它。这里的store和之前的router都可以理解为Vue的插件,都可以调用use方法。

    1. import { createApp } from 'vue'
    2. import App from './App.vue'
    3. import store from './store'
    4. createApp(App).use(store).mount('#app')

    好了,现在我们的项目中就可以通过VueX来进行全局的状态管理了,接下来我们要深入学习其中的核心概念!

    vue2 index.js

    npm i vuex@3

    1. import Vue from 'vue'
    2. import App from './App.vue'
    3. import store from './store'
    4. Vue.config.productionTip = false
    5. new Vue({
    6. render: h => h(App),
    7. store:store, // 可以简写
    8. }).$mount('#app')
    1. import Vue from 'vue'
    2. import VueX from 'vuex'
    3. Vue.use(VueX)
    4. const store = new VueX.Store({
    5. state(){
    6. return {
    7. count: 1,
    8. msg:'公共状态'
    9. }
    10. },
    11. mutations: {
    12. // 定义修改数据的方法
    13. addCount(state, payload) {
    14. state.count += payload.a
    15. }
    16. }
    17. })
    18. export default store
    1. <template>
    2. <div>
    3. <button @click="addCount">{{ count }}</button>
    4. <p>{{ msg }}</p>
    5. </div>
    6. </template>
    7. <script>
    8. // 映射函数,可以将公共状态映射到当前组件
    9. import { mapMutations, mapState } from 'vuex'
    10. export default {
    11. mounted() {
    12. console.log(this)
    13. },
    14. computed: {
    15. ...mapState({
    16. count: (state) => state.count,
    17. msg: (state) => state.msg, // 这里可以改名字,但是不推荐改
    18. })
    19. },
    20. methods: {
    21. add() {
    22. // 违背了数据单向流动的原则
    23. // this.$store.state.count++
    24. // this.$store.commit通过这个方法修改了公共状态值
    25. this.$store.commit('addCount', { a: 10, b: "haha" })
    26. }
    27. }
    28. }
    29. </script>

    2.state

    2.1 保存状态

    store中保存的状态同样是具有响应性的,我们把应用中的公共状态统一保存到state属性中。

    js

    1. const store = createStore({
    2. state() {
    3. return {
    4. count: 0, //这里的count后面就可以被应用中所有的组件共享
    5. }
    6. },
    7. })
    2.2 读取状态

    在项目中任意创建了一个Hello组件,现在希望在这个组件中取出刚才的count。由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

    在组件中获取到store必须调用useStore这个方法(和vue-router类似,在Vue3的最新生态中推荐使用函数的方式来获取示例,而非this)

    vue

    1. //Hello组件中
    2. <template>
    3. <h2>{{ count }}</h2>
    4. </template>
    5. <script setup>
    6. import { useStore } from 'vuex'
    7. import { computed } from '@vue/reactivity'
    8. let count = computed(() => {
    9. return useStore().state.count
    10. })
    11. </script>
    2.3 映射函数

    映射函数,可以将公共的状态映射到当前的组件,再在当前组件中调用就会简便很多。如果你使用的是Vue2版本的配置选项写法,使用映射函数往往非常高效。当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性:

    1. import { mapState } from 'vuex'
    2. export default {
    3. // ...
    4. computed: {
    5. ...mapState({
    6. // 箭头函数可使代码更简练
    7. count: state => state.count,
    8. // 传字符串参数 'count' 等同于 `state => state.count`
    9. countAlias: 'count',
    10. // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    11. countPlusLocalState (state) {
    12. return state.count + this.localCount
    13. }
    14. })
    15. }
    16. }
    2.4 组件独立的状态

    使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

    VUE2实现

    1. <template>
    2. <div>
    3. <button @click="addCount">{{ count }}</button>
    4. </div>
    5. </template>
    6. <script>
    7. import { mapMutations, mapState } from 'vuex'
    8. export default {
    9. methods: {
    10. ...mapMutations(["addCount"])
    11. }
    12. }
    13. </script>

    vuex中mutations里都是同步修改数据的方法, 如果希望异步修改数据,需要把对应的方法,定义在actions的对象中

    但是actions不能直接修改state,最终还是要通过触发mutations来实现。

    引用的方法同样可以映射

    1. <template>
    2. <div>
    3. <button @click="addCount">{{ count }}</button>
    4. <button @click="addAsyncCount(3)">{{ count }}</button>
    5. <!-- 异步按钮,三秒后响应,增加3 -->
    6. <p>{{ msg }}</p>
    7. </div>
    8. </template>
    9. <script>
    10. import { mapMutations, mapState, mapActions } from 'vuex'
    11. export default {
    12. mounted() {
    13. console.log(this)
    14. },
    15. computed: {
    16. ...mapState({
    17. count: (state) => state.count,
    18. msg: (state) => state.msg,
    19. })
    20. },
    21. methods: {
    22. ...mapMutations(["addCount"]),
    23. ...mapActions(["addAsyncCount"]),
    24. }
    25. }
    26. </script>

    3.getter

    3.1设置getters

    有时候我们需要从 store 中的 state 中派生出一些状态,通过计算属性即可实现,但如果有多个组件需要用到这个计算属性,我们要么复制这个计算属性,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

    Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。

    1. const store = createStore({
    2. state: {
    3. todos: [
    4. { id: 1, text: '...', done: true },
    5. { id: 2, text: '...', done: false }
    6. ]
    7. },
    8. getters: {
    9. doneTodos (state) {
    10. return state.todos.filter(todo => todo.done)
    11. }
    12. }
    13. })
    3.2 读取getters

    和state类似,同样可以在组件中通过store实例来读取到对应的getter。

    1. //Hello中
    2. let doneTodos = computed(()=>{
    3. return useStore().getters.doneTodos
    4. })
    3.3 映射函数

    mapGetters 辅助函数同样可以将 store 中的 getter 映射到局部计算属性中,如果你使用配置选项仍然推荐你这么做,在setup语法糖中这样使用的意义就没那么大了。

    1. import { mapGetters } from 'vuex'
    2. export default {
    3. computed: {
    4. // 使用对象展开运算符将 getter 混入 computed 对象中
    5. ...mapGetters([
    6. 'doneTodosCount',
    7. 'anotherGetter',
    8. ])
    9. }
    10. }

    VUE2实现

    4.mutation

    4.1 提交commit

    VueX规定必须通过mutation来能修改state中的状态。mutations中保存了修改了state的各种方法,在组件中可以通过store.commit来触发对应的方法,同时传入参数(这个参数叫做载荷)

    1. //store
    2. mutations: {
    3. increment(state, n) {
    4. state.count += n
    5. },
    6. },
    1. //Hello
    2. <template>
    3. <h2>{{ count }}</h2>
    4. <button @click="add">点我count增加</button>
    5. <div v-for="todo in doneTodos" :key="todo.id">
    6. <h3>{{todo.text}}</h3>
    7. </div>
    8. </template>
    9. <script setup>
    10. import { useStore } from 'vuex'
    11. import { computed } from '@vue/reactivity'
    12. const store = useStore()
    13. let count = computed(() => {
    14. return store.state.count
    15. })
    16. let doneTodos = computed(()=>{
    17. return store.getters.doneTodos
    18. })
    19. const add = ()=>{
    20. store.commit('increment',5)
    21. }
    22. </script>
    4.2 mutation必须是同步函数

    VueX是规定mutations中修改状态必须是同步的,我们可以尝试写一段异步代码:

    1. mutations: {
    2. increment(state, n) {
    3. setTimeout(() => {
    4. state.count += n
    5. }, 2000)
    6. },
    7. },

    大家肯定会疑问,代码没问题呀页面也能够正确响应状态的变化,但是VueX设计思想中需要能够捕捉到mutation前后状态的快照,异步代码无法让VueX能够了解其执行时机,这样描述可能大家此时仍然不好理解,所以现在你需要记住这个结论!

    5.action

    action和mutation有些类似,但不同在于:

    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作。

    Action 函数第一个参数接受一个与 store 实例具有相同方法和属性的 context对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

    Action第二个参数也接受一个载荷。

    1. actions:{
    2. increment(ctx,payload){
    3. setTimeout(()=>{
    4. ctx.commit('increment',payload)
    5. },2000)
    6. }
    7. }

    在组件中视图中,可以通过事件来触发action。所以之前的异步操作可以通过分发action来实现。

    1. const add = () => {
    2. store.dispatch('increment',2)
    3. }

    6.module

    6.1 嵌套模块

    由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

    为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

    1. const moduleA = {
    2. state: () => ({ ... }),
    3. mutations: { ... },
    4. actions: { ... },
    5. getters: { ... }
    6. }
    7. const moduleB = {
    8. state: () => ({ ... }),
    9. mutations: { ... },
    10. actions: { ... }
    11. }
    12. const store = createStore({
    13. modules: {
    14. a: moduleA,
    15. b: moduleB
    16. }
    17. })
    18. store.state.a // -> moduleA 的状态
    19. store.state.b // -> moduleB 的状态
    6.2 命名空间

    默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间。必须注意,不要在不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。

    如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:

    1. const store = createStore({
    2. modules: {
    3. account: {
    4. namespaced: true,
    5. // 模块内容(module assets)
    6. state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
    7. getters: {
    8. isAdmin () { ... } // -> getters['account/isAdmin']
    9. },
    10. actions: {
    11. login () { ... } // -> dispatch('account/login')
    12. },
    13. mutations: {
    14. login () { ... } // -> commit('account/login')
    15. },
    16. // 嵌套模块
    17. modules: {
    18. // 继承父模块的命名空间
    19. myPage: {
    20. state: () => ({ ... }),
    21. getters: {
    22. profile () { ... } // -> getters['account/profile']
    23. }
    24. },
    25. }
    26. }
    27. }
    28. })

    命名空间在多人协作处理大型应用的时候还是非常具有实际意义的。

    vue2实现

    分别为login和profile新建store项目,可以分别为不同的组件书写不同的store

    1. import Vue from 'vue'
    2. import VueX from 'vuex'
    3. import loginStore from "./loginStore";
    4. import profileStore from "./profileStore";
    5. Vue.use(VueX)
    6. const store = new VueX.Store({
    7. modules:{
    8. login:loginStore,
    9. profile:profileStore,
    10. }
    11. })
    12. export default store

    在app中可以通过模板来获取到子组件store中的值

    1. // 代表Store中modules下,login这个store中a元素的值
    2. <p>{{ $store.state.login.a }}</p>

    在store中,存放需要在组件中流通的公共状态

    不需要流通的状态和属性还是仍然放在原本的组件中

  • 相关阅读:
    十一、2023.10.5.计算机网络(end).11
    Java本地远程断点调试(实战记录)
    基于ssm卤菜销售系统
    NR 物理层 卷积2
    QT进度条 QProgressDialog基础、高级和样式表使用详解
    OpenSergo & ShardingSphere 社区共建微服务视角的数据库治理标准
    有限自动机字符串匹配
    c#中switch常用模式
    mysql日志总结
    契约锁助力大型医院常用文件电子签:检验-诊疗-住院全程无纸化
  • 原文地址:https://blog.csdn.net/albedo_102/article/details/132927932