likes
comments
collection
share

对 Vuex 的理解

作者站长头像
站长
· 阅读数 47

理解 Vuex

Vuex 在一般的 Vue 项目中,比较常用,一般拿来实现数据的持久化,上次面试的时候面试官让我讲一讲对 Vuex 的理解(mutation 和 action 的区别),平时的话用是会用,但到了真正阐述对 Vuex 的理解的时候,却有点说不太上来......

什么是 Vuex ?

什么是 Vuex ?官方文档给出的描述是:" Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 ",其核心包括 stategettersmutationsactionsmodules

Vuex 可以解决什么问题?

在一个复杂的 Vue.js 应用中,不同的组件可能需要共享相同的数据或状态,但是组件之间相互独立,没有直接的通信方式。这时,如果直接使用组件之间的传递数据来管理状态,会导致代码冗长且维护困难,同时也会破坏单向数据流的思想。此时,使用 Vuex 可以有效地解决这个问题,Vuex 通过一个集中式的状态管理机制,让多个组件共享同一个状态,并能在不同组件之间进行数据的传递和操作,使得代码更加清晰、易于维护,同时也避免了数据状态的混乱和冲突。因此,Vuex 主要解决在 Vue 项目中状态(数据)的管理和共享问题。

Vuex 的 5 个核心属性

Vuex 核心包括 stategettersmutationsactionsmodules

state:

基本概念及用法

state 是存储共享状态的核心部分,类似于组件中的 data ,用于存储整个应用程序的状态。Vuex 的状态是响应式的,当 state 发生变化时,所有依赖于 state 的组件都会自动更新。state 在 Vuex 中的定义如下:

const store = new Vuex.Store({
  state: {
    user: {
      name: 'PandaGuo',
      identity: 'student'
    }
  }
})

在 main 文件中挂载完 Vuex 后,可以通过 this.$store.state 来访问 state 中定义的数据:

const user = this.$store.state.user;
console.log(user.name);  // PandaGuo

但是在 Vue 中,不建议对 state 中的数据直接进行修改(不容易追踪数据的变化、破坏单向数据流的规则,使得应用程序变得混乱、可能导致意外情况发生),一般使用 mutationstate 中的数据进行修改。

mapState 辅助函数

开发过程中如果需要一次性取出 state 中的多个数据,直接写多个 this.$store.state 获取数据会显得代码冗长,写起来也不方便,这时候就可以使用 mapState 辅助函数:该方法可以将 state 中的数据映射到组件的计算属性中。具体使用方法如下:

state 中定义好数据:

    const store = new Vuex.Store({
        state: {
            name: 'PandaGuo',
            age: 22,
            skill: ['Html', 'Css', 'JavaScript', 'Vue', 'Uni-app']
        }
    })

在全局挂载好 Vuex 的主文件之后,在其他组件引入 mapState 辅助函数,利用对象展开运算符将state混入computed对象中:

  import { mapState } from "vuex";
  export default {
    created() {
      console.log(this.name); // PandaGuo
      console.log(this.age);  // 22
      console.log(this.skill);  // [ "Html", "Css", "JavaScript", "Vue", "Uni-app" ]
    },
    computed: {
      ...mapState(['name', 'age', 'skill'])
    }
  }

getters:

基本概念及用法:

getters 类似于组件中的 computed ,它们可以接收 state 作为第一个参数,其它 getters 作为第二个参数,而且它们会被缓存起来,只有在依赖的状态发生变化时才会重新计算。getters 可以帮助我们派生出一些状态,以便在多个组件中共享这些状态,而不需要每次都重复计算。具体使用方法如下:

const store = new Vuex.Store({
     state: {
         name: 'PandaGuo',
         age: 22,
         skill: ['Html', 'Css', 'JavaScript', 'Vue', 'Uni-app']
     },
     getters: {
         getCss(state) {  // 接收一个参数
             return state.skill[1];
         },
         showSkill(state, getters) { // 接收多个参数
             return `${state.name} can use ${getters.getCss}`;
         }
     }
 })
export default {
      created() {
        console.log(this.$store.getters.getCss);  // Css
        console.log(this.$store.getters.showSkill); //  PandaGuo can use Css
      }
    }

使用 gettter 返回带参函数:

getter 还可以返回一个函数来实现给 getter 传参。然后通过参数来进行判断从而获取 state 中满足要求的状态,具体实现代码如下:

const store = new Vuex.Store({
        state: {
            toDoList: [
                { id: 1, task: '归纳面试题'},
                { id: 2, task: '维护旧项目'}
            ]
        },
        getters: {
            getTask: (state) => (id) => {
                return state.toDoList.find(item => item.id == id);
    }
        }
    })

export default {
      created() {
        console.log(this.$store.getters.getTask(2));  // {id: 2, task: '维护旧项目'}
      }
    }

mapGetters 辅助函数:

mapState 辅助函数类似,mapGetters 可以将多个 getter 映射为计算属性并输入到当前组件的 computed 中,这样就可以通过直接访问计算属性,来获取各个 getter 的结果,达到简化代码效果,具体实现代码如下:

 const store = new Vuex.Store({
        state: {
            toDoList: [
                { id: 1, task: '归纳面试题'},
                { id: 2, task: '维护旧项目'}
            ],
            name: 'PandaGuo'
        },
        getters: {
            getTask: (state) => (id) => {
                return state.toDoList.find(item => item.id == id);
    },
            getName(state) {
                return state.name;
            }
        }
    })
import { mapGetters } from "vuex";

    export default {
      created() {
        console.log(this.getTask(2));  // {id: 2, task: '维护旧项目'}
        console.log(this.getName);  // PandaGuo
      },
      computed: {
          ...mapGetters(['getTask', "getName"])
      }
    }

mutation:

基本概念及用法:

在前面提到过 state 中的数据状态不建议直接进行修改,最好的方法就是使用 mutation,mutation 是用来修改 state 状态的唯一途径,它们用于改变 state 中的数据,可以看作是一种提交更改的事件。这个事件由从 Vuex 创建的全局存储引发。

在编写 mutation 时,我们只能使用同步的代码逻辑,因为修改 state 前必须等待前一个 mutation 行完成。在应用中,我们不能直接调用 mutation ,而是要用其映射方法(例如 commit()、mapMutations() 等)进行调用。mutation 还可以传入参数,第一个参数是 state,第二个参数是载荷(payLoad),也就是额外的参数。mutation 具体使用代码如下:

const store = new Vuex.Store({
        state: {
            name: 'GGBond'
        },
        mutations: {
            setName(state, payload) {
                state.name = payload;
            }
        }
    })
export default {
      created() {
        console.log(this.$store.state.name);  // GGBond
        this.$store.commit('setName', 'PandaGuo');  
        console.log(this.$store.state.name);  // PandaGuo
      }
    }

mapMutations 辅助函数:

mapMutationsmapStatemapGetters 一样,但有点不同的是,mapMutations 是将所有 mutations 里面的方法映射为实例 methods 里面的方法,具体实现代码如下:

const store = new Vuex.Store({
        state: {
            name: 'GGBond',
            age: 18
        },
        mutations: {
            setName(state, payload) {
                state.name = payload;
            },
            setAge(state, payload) {
                state.age = payload;
            }
        }
    })
export default {
      created() {
        this.setName('PandaGuo');
        this.setAge(22);
        console.log(this.$store.state.name);  // PandaGuo
        console.log(this.$store.state.age);  // 22
      },
      methods: {
        ...mapMutations(['setName', 'setAge'])
      }
    }

action:

基本概念及用法:

actionmutation 类似,也可以用于更新 state 中的数据状态,但是与 mutation 不同,action 是用于提交 mutation 而不是直接变更 state。通常情况,action 可以处理异步操作(不建议在 mutation 中进行异步操作), action 中默认的方法都是异步的,且返回 Promiseaction 中一些常见的方法参数如下:

  1. context:上下文对象,包含了 commit()dispatch() 以及 getter 等操作。可以通过 context.commit() 来提交 Mutation,也可以通过 context.dispatch() 触发其他 Action
  2. state:当前的状态对象,即 store.state。在有些情况下,我们需要直接访问 state 对象来进行一些操作。
  3. payload:负载数据,即由组件传递过来的数据,用于在 Action 中更新状态。可以通过在 dispatch 方法中传入附加参数来传递数据。
  4. getter:类似于 store.getters,用于访问全局 store 中定义的 getter 函数。
  5. 其他的一些对象和方法:例如 rootStaterootGettersrootCommit() 等,用于从根 state 和根 getters 访问全局 store

action 在组件中是使用 dispatch 来进行调用,具体实现代码如下:

 const store = new Vuex.Store({
      state: {
          name: 'GGBond',
      },
      mutations: {
          setName(state, payload) {
              state.name = payload;
          }
      },
      actions: {
          setNameAsync(context, name) {
              context.commit('setName', name);
          }
      }
  })

在组件中使用 dispatch 调用 action 中的方法:

 export default {
   methods: {
       changeName() {
         this.$store.dispatch('setNameAsync', 'PandaGuo');  // 使用 dispatch 调用
       }
   }
 }

最后将方法绑定到按钮上,点击按钮修改数据即可完成异步修改:

<template>
     {{ $store.state.name }}
     <button @click="changeName">change name</button>
 </template>

点击按钮进行修改,页面上的 " GGBond " 会修改成 " PandaGuo "。

mapActions 辅助函数:

mapActions 和 mapMutations 一样,它将 actions 里面的方法映射到 methods,当 action 多了可以使用 mapActions 简化操作,具体实现代码如下:

const store = new Vuex.Store({
       state: {
           name: 'GGBond',
           gender: undefined,
           age: 18
       },
       mutations: {
           setName(state, payload) {
               state.name = payload;
           },
           setGender(state, payload) {
               state.gender = payload;
           },
           setAge(state, payload) {
               state.age = payload;
           }
       },
       actions: {
           setNameAsync(context, name) {
               context.commit('setName', name);
           },
           setGenderAsync(context, sex) {
               context.commit('setGender', sex);
           },
           setAgeAsync(context, age) {
               context.commit('setAge', age);
           }
       }
   })
 export default {
      methods: {
          ...mapActions(['setNameAsync', 'setGenderAsync', 'setAgeAsync']),
        changeInfo() {
            this.setNameAsync('PandaGuo');
            this.setGenderAsync('男');
            this.setAgeAsync(22);
        }
      }
    }

   <template>
       {{ $store.state.name }}
       {{ $store.state.gender }}
       {{ $store.state.age }}
       <button @click="changeInfo">change name</button>
   </template>

最终点击按钮会异步修改页面上的相关数据: " PandaGuo 男 22 "。

module:

基本概念及用法:

企业级大项目通常需要存储更多的数据状态,如果都存储到一个 store 对象会显得臃肿且不易维护,这时候就可以使用 module 将 store 分割成小模块,这些模块在需要使用时被动态添加到 Vuex Store 当中。具体实现代码如下:

  const userModule = {
    namespaced: true, // 告诉 Vuex 把 userModule 当做带命名空间的模块来处理
    state: {
      userInfo: {
        name: 'PandaGuo',
        age: 22,
        gender: 'male'
      }
    },
    mutations: {
      setName(state, name) {
        state.userInfo.name = name;
      },
      setAge(state, age) {
        state.userInfo.age = age;
      },
      setGender(state, gender) {
        state.userInfo.gender = gender;
      }
    },
    actions: {
      fetchUserInfo(context) {
        // 模拟异步请求用户信息的过程
        setTimeout(() => {
          context.commit('setName', 'Tom');
          context.commit('setAge', 18);
          context.commit('setGender', 'female');
        }, 1000);
      }
    },
    getters: {
      userInfoStr(state) {
        return `Name: ${state.userInfo.name}, Age: ${state.userInfo.age}, Gender: ${state.userInfo.gender}`;
      }
    }
  }
  const store = new Vuex.Store({
    modules: {
      user: userModule // 将上述 userModule 注册到 store 的 modules 属性中。
    }
  });

在组件具体使用某一个模块的方法:

export default {
    methods: {
      ...mapActions('user', ['fetchUserInfo'])
    },
    created() {
      this.fetchUserInfo();
    }
  }

action 与 mutation 的区别:

上次面试官问了我这个问题,我只能答出同步和异步的区别,后面再挖掘就答不上来了......

  • action 提交的是 mutation,而不是直接变更状态,mutation 可以直接变更状态;
  • action 可以包含任意的异步操作,mutation 只能进行同步操作;
  • action 使用的是 dispatch 进行提交,而 mutation 使用的是 commit 进行提交;
  • mutation 第一个参数是 state ,而 action 第一个参数是 context(上下文对象),其中包含了:
{
    state,      // 等同于 `store.state`,若在模块中则为局部状态
    rootState,  // 等同于 `store.state`,只存在于模块中
    commit,     // 等同于 `store.commit`
    dispatch,   // 等同于 `store.dispatch`
    getters,    // 等同于 `store.getters`
    rootGetters // 等同于 `store.getters`,只存在于模块中
}

mutation 中是否可以进行异步操作?

上次面试官问了我这个问题,一开始我的回答是否,mutation 只能进行同步操作,但后面面试官说其实是可以的,只是不建议这么做。在官方文档中有提到这一点:" 在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,mutation 都是同步事务 "

参考资料:

【字节跳动】前端面试准备(一) | ChoDocs

转载自:https://juejin.cn/post/7242499021170884667
评论
请登录