Vue2总结(二)Vuex

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Vue2总结(二)Vuex

趁着Vue3还没发布,先把vue2的各个知识点、源码、轮子全部温习一遍。 友情提示:阅读本文大概需要** 30****分钟**

前言

常见的前端状态管理库有Redux、mobX、Vuex、以及上次介绍的Fish-redux。其中Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。通俗一点理解即:针对组件繁多交互复杂的单页面应用,Vuex提供了一种便利、准确和可预测的状态管理方式,方便组件之间的数据共享和修改。

Vue2总结(二)Vuex

Vuex

在SPA单页面组件的开发中 Vue的vuex和React的Redux 都统称为同一状态管理,个人的理解是全局状态管理更合适;简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、进行修改,并且你的修改可以得到全局的响应变更。


// Vue-cli3 可以在初始化时勾选 vuex
// 也可以自定义安装
cnpm i vuex --save
初级使用方法

// main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import Vuex from 'vuex' // 引入vuex

Vue.config.productionTip = false
Vue.use(Vuex);

let store = new Vuex.Store({ // store 对象
  state:{
    count:0
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store, //使用store,这可以把 store 的实例注入所有的子组件
  components: { App },
  template: 'App/'
})


/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store, //使用store,这可以把 store 的实例注入所有的子组件
  components: { App },
  template: 'App/'
})

此时可以在组件中使用 this.$store.state.count 获取store中state的值。如:


// 在组件的computed中使用
  computed:{
     count(){
      return this.$store.state.count;
     }
  }

想想一下当项目比较大的时候数据繁琐,如果按照上述方法使用vuex,当你打开main.js你看的到场景是比较混乱的,各种数据繁杂在一起,不便于日后的维护。因为在大多数的项目中,我们对于全局状态的管理并不仅仅一种情况的需求,有时有多方面的需求,比如写一个商城项目,你所用到的全局 state 可能是关于购物车这一块儿的也有可能是关于商品价格这一块儿的;像这样的情况我们就要考虑使用vuex中的 modules 模块化了,请看下一步:

2.modules 模块化
State 用法

// 在 main.js 删除下述这部分代码
let store = new Vuex.Store({ // store 对象
  state:{
    count:0
  }
})

// 新建 store/index.js 并写入,别忘了在main.js引入 import store from './store'
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
 strict:true,  // 开启严格模式  确保state 中的数据只能 mutations 修改
 const state={   //要设置的全局访问的state对象
     count:0//下面举栗子用
     showFooter: true,
     changableNum:0
     //要设置的初始属性值
   };
const getters = {   //实时监听state值的变化(最新状态)
    isShow(state) {  //方法名随意,主要是来承载变化的showFooter的值
       return state.showFooter
    },
    getChangedNum(){  //方法名随意,主要是用来承载变化的changableNum的值
       return state.changebleNum
    }
};
const store = new Vuex.Store({
       state,
       getters
});
export default store;

// 测试: 可以在组件中使用了
template
  div class="hello"
    h2{{count}}/h2
  /div
/template

script
export default {
  name: 'HelloWorld',
  computed:{
     count(){
       return this.$store.state.count;
     }
  }
}
/script

Mutations 用法

很多时候咱们要对state里的值进行操作,在vuex提供了一个方法mutations,mutations用法(使用mutations可以修改state的值)。


// mutations 用法mutations v uex提供了一个方法mutationsvuexvuex提供了一个方法mutations提供了一个方法mutations
  state:{
    count:0
  },
  mutations:{ // 更改数据的方法
    add(state){
      state.count++
    },
    //提交载荷用法
    //add(state,n){  
    // state.count += n
    //},
    sub(state){
      state.count--
    }
  }

组件(HelloWorld.vue)中使用mutations里对应的方法:


template
  div class="hello"
    button @click="add"+/button
    h2{{count}}/h2
    button @click="sub"-/button
  /div
/template

script
export default {
  name: 'HelloWorld',
  computed:{
     count(){
       return this.$store.state.count;
     }
  },
    methods:{
    add(){
      this.$store.commit('add');
    },

    //提交载荷用法
    // add(){  
    //    this.$store.commit('add',10);
    // },

    //对象风格的提交方式
    //   store.commit({
    //     type: 'add',
    //     n: 10
    //     })

    sub(){
      this.$store.commit('sub');
    }
  }
}
/script

此时就可以对count进行修改了,当你想异步操作的时候,由于mutation必须是同步的这一点,此时不能采用mutation对state 进行修改。action派上用场了,action就是一个函数集合,在里面怎么操作都可以,只要最后触发mutation 就可以了。


mutations: {
   add (state) {
     api.callAsyncMethod(() = {
    state.count++
   })
  }
}

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

Action 用法

// soreindex.js
  mutations:{ // 更改数据的方法
    add(state){
      state.count++
    },
    sub(state){
      state.count--
    }
  },
  actions:{
    add(context){  // context 与 store 实例具有相同方法和属性(但不是store 实例)
      setTimeout(()={
        context.commit('add');
      },1000)
    }
  }


// 组件中使用getters里的方法
template
  div class="hello"
    button @click="add"+/button
    button @click="add_action"action +/button
    h2{{count}}/h2
    button @click="sub"-/button
    div
      test: {{doneTodos[0].text}} br
      length: {{doneTodosLength}}
    /div
  /div
/template
export default {
  methods:{
    add(){
      this.$store.commit('add');
      // console.log(this);
    },
    sub(){
      this.$store.commit('sub');
    },
    add_action(){
      this.$store.dispatch('add');
    }
  }
}

看到这里有没有想过当我们使用state中某一个数据时,我们只想用该数据中符合条件的数据。比如:


state:{
    count:0,
    todos: [
      { id: 1, text: 'text1--true', done: true },
      { id: 2, text: 'text2--false', done: false }
    ]
  }

此时我们只想获取state.todos中done为true的数据时我们应该怎么获取?可能会有以下两种方案:

  • **每个在组件中首先获取todos,然后使用filter方法过滤;**
  • **写一个公共函数在每个组件中调用以下;**
  • 如果用到todos中done为true的组件很多,这两种方法都是很不理想的。Vuex为此为我们引入了一个方法Getter。

    Getter 用法

    Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

    
    // soreindex.js
      mutations:{ // 更改数据的方法
        add(state){
          state.count++
        },
        sub(state){
          state.count--
        }
      },
      getters:{  // 用法类似组件中的 computed, 可以认为是store的计算属性
        doneTodos:state = { // Getter 接受 state 作为其第一个参数:
          return state.todos.filter(todo = todo.done)  // - [{ id: 1, text: 'text1--
    
    true', done: true }]
        },
        // Getter 也可以接受其他 getter 作为第二个参数
        doneTodosLength:(state,getters) = {
          return getters.doneTodos.length // - 1
        },
      }
    
    // 组件中使用getters里的方法:
    template
      div class="hello"
        button @click="add"+/button
        h2{{count}}/h2
        button @click="sub"-/button
        div
          test: {{doneTodos[0].text}} br
          length: {{doneTodosLength}}
        /div
      /div
    /template
    script
    export default {
        computed:{
            doneTodos(){
              return this.$store.getters.doneTodos 
            },
            doneTodosLength(){
              return this.$store.getters.doneTodosLength 
            }
        }
    }
    /script
    

    Vuex状态持久化

    Vue2总结(二)Vuex

    就像session一样,很多项目数据是从服务器请求获取到的,比如token、还有很多api请求到的数据,用户id、用户等级、用户积分、历史足迹等等。有些敏感数据不适合放在浏览器存储里(比如cookie、localStorage等等),而且,浏览器前端存储更适合页面之间的传递,而vuex等状态时每个组件都能实时获取和改变,每个组件都能获取到state的最新值,而组件只会获取加载时的localStorage值,如果在页面没有跳转或重载的情况下 Storage的值发生变化,页面不会及时发生变化。

    举个简单的栗子: 在login登陆页面,如果账户验证成功,但是页面逻辑没有发生跳转(URL没有发生变化),vuex里的isLogin已经变成true,同时持久化localStorage里的isLogin也变成true(默认是false),那么只要这个页面没有跳转和重载,它获取到的localStorage里的isLogin还是false,简单一句话,localStorage不是实时最新的。

    我们平常会用到 持久化手段把vuex、redux或mobX里的state放在cookie或localStorage里去,方便作一些逻辑处理,方便做页面缓存。比如默认的token是放在cookie里的,当你授权(登录)一个程序时,他就是个依据,判断你是否已经授权该软件;cookie就是写在客户端的一个txt文件,里面包括你登录信息之类的,这样你下次在登录某个网站,就会自动调用cookie自动登录用户名。我们将类似的数据放进cookie等里,不仅仅是token,用户名或玩家等级等等。

    持久化的目的:刷新不丢数据,有些状态仍可维持

    
    // 下面代码不规范,仅做一个小栗子
    // vuex的持久化:引入vuex-persistedstate插件
    npm install vuex-persistedstate --save
    
    // src/store/index.js
    import createPersistedState from "vuex-persistedstate"
    conststore = newVuex.Store({  
        // ...  
        plugins: 
        [createPersistedState()
        ]
    })
    // 默认存储到localStorage
    
    // 存储到sessionStorage
    import createPersistedState from "vuex-persistedstate"conststore = newVuex.Store({    
        // ...    
        plugins: 
        [createPersistedState({   
           storage:window.sessionStorage   
           })
        ]
    })
    // 默认持久化所有state
    
    // 指定需要持久化的state
    import createPersistedState from "vuex-persistedstate"
    conststore = newVuex.Store({  
        // ...  
        plugins: 
        [createPersistedState({  
        storage:window.sessionStorage,      
            reducer(val)  {          
                return {              
                // 只储存state中的assessmentData                  assessmentData: 
        val.assessmentData          
        }      
       }  
      })
     ]
    })
    
    // 完成demo
    // 目前 需要状态中心管理的 数据不多,暂不使用modules/app和user分割
    import Vue from 'vue'
    import Vuex from 'vuex'
    import VuexPersistence from 'vuex-persist'
    Vue.use(Vuex)
    
    // vuex持久化
    const vuexLocal = new VuexPersistence({
        storage: window.localStorage
    })
    
    const state = {
        AllState: false,
        name: ''
    }
    
    const mutations = {
        // 登陆后右上角的状态改变
        changeState(state, username){
            state.AllState = true
            state.name = username
        },
        saveAdminInfo(state, adminInfo){
            state.adminInfo = adminInfo;
        }
    }
    
    const actions = {
        async getAdminData({commit}){
            try{
                const res = await getAdminInfo()
                if (res.status == 1) {
                    commit('saveAdminInfo', res.data);
                }else{
                    throw new Error(res.type)
                }
            }catch(err){
                // console.log(err.message)
            }
        }
    }
    
    export default new Vuex.Store({
        state,
        actions,
        mutations,
        plugins: [vuexLocal.plugin]
    })
    

    Vuex闲置状态重置

    针对SPA项目,我们往往会通过使用状态管理器 vuex 去解决组件间状态共享与状态传递等问题。这种应用少则几十个单页,多则上百个单页。随着路由的频繁切换,每个路由对应的 vuex 中的状态将越来越多。为了做到页面的极致优化,我们需要将那些闲置的状态重置,适当的时机清除vuex 中不用的状态,否则大型单页面应用路由多次切换以后内存将持续上升。vuex 强调采用集中式存储管理应用的所有组件的状态,但是我们真把 所有的状态都放到 store 中去处理,你会发现开发起来非常痛苦。这里如果想很好的把控哪些数据需要放到 store 中去管理,首先要理解 vuex 是用来解决什么问题的。vuex 官网指出是为了解决多个组件共享状态的,那么我们就可以把多个组件的共享状态放到 store 中去管理,这里的多组件共享对于单页应用很多情况是跨路由的组件。如果 store只存储多组件共享的状态,那么我们就没必要去清理 vuex 中的状态了,因为这些状态随时会被用到。

    我们想要的效果是在路由切换的时候,把上一个路由对应的 vuex 中的状态重置掉,但是路由和vuex 并没有一一对应的关系,如果要做到这种效果,那么我们需要维护一个路由与vuex 模块的对应关系,这样会很繁琐。不如当路由改变时去重置 vuex 中的所有状态。

    下面的栗子中采用上文所描述的模块化的方式,将路由对应的组件状态放到对应的 module 中,多组件共享的状态放到顶级的 store 中管理。请看代码:

    
    // store/index.js
    import page1 from "./modules/page1.js";
    import page2 from "./modules/page2.js";
    import page3 from "./modules/page3.js";
    import page4 from "./modules/page4.js";
    import page5 from "./modules/page5.js";
    
    export default new Vuex.Store({
        state,
        getters,
        actions,
        mutations,
        modules: { // 每个路由对应的 module
            page1,
            page2,
            page3,
            page4,
            page5
        },
        plugins: __DEV__ ? [createLogger()] : [],
        strict: __DEV__ ? true : false
    });
    
    // 例如 store/modules/page1.js
    const state = {
         // 列表数据
         page1Data: [],
         // 标题数据
         page1Title: ''
    }
    

    这些数据是通过调用后端 api 返回并复制的数据,如果我们在路由改变的时候重置这些数据,那么需要将初始化数据提取出来,并且暴露一个需要重置的标识方法 initState(),代表路由改变的时候需要重置,当然这个方法名称是个约定,你也可以定义为其他名称。改造后为:

    
    // store/modules/page1.js
    // 放置你要重置的数据
    const initState = {
        page1Data: [],
    }
    
    // state
    const state = {
        // 参数解构
        ...initState,
    
        // 路由改变不想重置的数据
        page1Title: '',
        initState(){
            return initState
        }
    }
    

    定义全局 module 配置

    
    // 定义全局 mutation 事件类型
    // store/types.js
    export const RESET_STATES = 'resetStates'
    
    
    // 定义全局 mutation
    // store/mutation.js
    import * as types from './types'
    // 检测所有的 state 并把 `initState()` 中的属性重置
    function resetState(state, moduleState) {
        const mState = state[moduleState];
        if (mState.initState && typeof mState.initState === 'function') {
            const initState = mState.initState();
            for (const key in initState) {
                mState[key] = initState[key];
            }
        }
    
    }
    
    export default {
        [types.RESET_STATES](state, payload) {
            for (const moduleState in state) {
                resetState(state, moduleState);
            }
        },
    }
    
    // 定义全局 action
    // store/action.js
    import * as types from './types'
    export default {
        // rest state action
        resetStates:function (context, payLoad) {
            context.commit(types.RESET_STATES, payLoad);
        }
    }
    

    路由切换触发重置方法在至此一切准备就绪,只需要在路由改变时触发重置的方法即可,在入口 vue 文件中处理。

    // components/app.vue

    script
        import {mapState, mapActions} from “vuex”
        export default{
            methods: {
                …mapActions({
                    resetStates: “resetStates”
                })
            },
            watch: {
                $route(to, from) {
                    // 路由改变发起重置
                    this.resetStates();
                }
            }
        }
    /script

    如果你的 chrome 浏览器安装了 vuejs-devtools 在路由切换的时候就能够很清晰的看到上一个路由数据的的重置过程。我们这里的 vuex 状态重置,是每次路由切换遍历所有的 store 中的状态,并把initState() 中的属性重置,如果能做到把当前的路由对应的 state 重置就更好了,但是路由和 store 中的 module 并没有关联关系。

    参考

    1.朴树 - 链接:https://juejin.im/post/5bf7c4375188254b9d0935c9

    2.huangshuwei - 链接https://github.com/huangshuwei/vuex-reset-state

    最后

    今天的 Vue2总结(二)Vuex 就分享到这里,我的公众号没有留言功能哈,有问题大家心里默念,我能感受到,谢谢 ~ 

    原文始发于微信公众号(程序员思语):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> Vue2总结(二)Vuex


      转载请注明: 好好学java Vue2总结(二)Vuex

     上一篇
    Vue2总结(三)组件通信 Vue2总结(三)组件通信
    趁着Vue3还没发布,先把vue2的各个知识点、源码、轮子全部温习一遍。 友情提示:阅读本文大概需要** 40****分钟** 前言本篇文章将复习 Vue.js 父子组件之间通信的九种方式,无可否认,现在无论大厂还是小厂都已经用上了
    2021-04-05
    下一篇 
    Vue2总结(一)axios Vue2总结(一)axios
    趁着Vue3还没发布,先把vue2的各个知识点、源码、轮子全部温习一遍。 友情提示:阅读本文大概需要** 30****分钟** 前言axios 是基于 promise 用于浏览器和 node.js 是 http 客户端。axios的
    2021-04-05