5分钟快速上手Vuex+状态持久化,看就懂

176 阅读4分钟

5分钟快速了解Vuex和Vuex的用法+状态持久化

介绍

Vuex 应用的核心就是 store(仓库),它包含了应用中的大部分状态。 Vuex的状态存储是响应式的,改变store中的的状态唯一途径就是通过mutation

1.state

管理Vuex中所有状态存储,类似Vue 实例中的 data

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态

store/inde.js中

const store = createStore({
    state: {
        count:1
    }
)}

在 Vue 组件中获得 Vuex 状态

方式一:(组件获取少的状态)

computed: {
    count () {
        return this.$store.state.count
    }
}

方式二:利用mapState(组件需要获取多个状态)

import { mapState } from 'vuex'
export default {
  data() {
    return {
      num: 10,
    };
  },
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,

    // or传字符串参数 'count' 等同于 `state => state.count`
    myCount: 'count',

    // or为了能够使用 `this` 获取局部状态,必须使用常规函数
    countFn (state) {
      return  this.num + state.count
    }
  })
}

当映射的计算属性的名称与 state 的子节点名称相同

import { mapState } from 'vuex'
export default {
  computed: mapState({
     // 映射 this.count 为 store.state.count
     'count'
  })
}

2.getter

相当于store 的计算属性(用来处理数据)

可接受传参(state,getters)

store/inde.js中

const store = createStore({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    // 把state传进出(一个参数)
    doneTodos: (state) => {
      return state.todos.filter(todo => todo.done) // { id: 1, text: '...', done: true }
    },
    // 把getters传进去(两个参数)
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length // 1
    },
    // getter 也可返回一个函数,来实现给 getter 传参(两个参数)
    getTodoById: (state) => {
        (id) => {return state.todos.find(todo => todo.id === id)}
    }
  }
})

在 Vue 组件中获得 Vuex 状态

方式一:(组件获取少的状态)

computed: {
  doneTodos () {
    return this.$store.getters.doneTodos // { id: 1, text: '...', done: true }
  },
  getTodoById () {
    return this.$store.getters.getTodoById(2) // { id: 2, text: '...', done: false }
  }, 
}

方式二:利用mapGetters(组件需要获取多个状态)

将 store 中的 getter 映射到局部计算属性

import { mapGetters } from 'vuex'

export default {
  computed: {
  	// 使用对象展开运算符将 getter 混入 computed 对象中
    // 同名写法
    ...mapGetters([
      'doneTodos',
      'getTodoById'
    ])
    // 不同名写法(把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`)
    ...mapGetters({
  	  doneCount: 'doneTodosCount'
	})
  }
}

3.mutation(同步,通过commit触发)

更改 Vuex 的 store 中的状态的唯一方法(面试常问!!:因为如果通过mutation(同步函数)每一条都会被记录,devtools 都需能捕捉到前、后状态。如果是异步,就无法实现了,devtools就无法追踪到了,回调函数中进行的状态的改变都是不可追踪的)

可接受传参(state,(Payload))

store/inde.js中

const store = createStore({
  state: {
    num: 1,
    num2:100,
    num3:50
  },
  mutations: {
    addNum(state,n) {
      // 变更状态
      state.num += n
    },
    subNum(state) {
      // 变更状态
      state.num2--
    },
    addOne(state) 
      // 变更状态
      state.num3++
    },
  }
})

在 Vue 组件中获得 Vuex 状态

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'addOne', // 将 `this.addOne()` 映射为 `this.$store.commit('addOne')`

      // `mapMutations` 也支持载荷:
      'addNum' // 将 `this.addNum(num)` 映射为 `this.$store.commit('addNum', num)`
    ]),
    ...mapMutations({
      reduce: 'subNum' // 将 `this.reduce()` 映射为 `this.$store.commit('subNum')`
    })
  }
}

==补充==

也可以使用常量当做变量名,多人协作时知道是干啥的函数(正常情况大可不用)

mutation-types.js

export const ADD_MUTATION = '对数字进行加法运算';

store→inde.js中

import * as types from './mutation-types';
const store = createStore({
  state: {
    num: 1
  },
  mutations: {
    [types.ADD_MUTATION] (state,n) {
      // 变更状态
      state.num += n
    }
  }
})

4.action(异步,通过dispath触发)

也是通过提交的是 mutation进行改变,而不是直接变更状态

可以包含任意异步操作

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象参数(但它并不是store)

store/inde.js中

方法一:利用context参数

const store = createStore({
  state: {
    num: 0
  },
  mutations: {
    addNum (state) {
      state.num++
    }
  },
  actions: {
    increment (context) {
      // 也可通过context.state 和 context.getters 来获取 state 和 getters
      // 此处context.commit获取mutation中的addNum方法
      context.commit('addNum')
    }
  }
})

方法二:参数解构来简化代码

import { userInfo } from '@/api/common';
const store = createStore({
  state: {
    num: 0,
    userInfo:{age:18,name:'圆圆'}
  },
  mutations: {
    addNum (state) {
      state.num++
    },
    userInfos(state,info){
      state.userInfos=info
    }
  },
  actions: {
    increment ({ commit,state,dispatch }) {
      // commit:可调用mutation里面的方法修改state
      // state:当前action中此方法使用到state中的数据
      // dispatch:可调用actions中的其他异步方法
      commit('addNum')
    },
    getUserInfo({ commit,dispatch }) {
      await dispatch('increment') // 等待 increment 完成后,再执行下面代码(这是是为了说明可内部调用action中的其他方法)
      return new Promise((resolve, reject) => {
		userInfo().then((res) => {// 这里的userInfo为请求的接口、userInfo() 返回的是 Promise
		  commit('userInfos', res);
		  resolve(res);
		});
       });
    },
  }
})

在 Vue 组件中获得 Vuex 状态

方法一:

export default {
  async created() {
      await this.$store.dispatch('increment'),// 根据情况添加async和await
      this.$store.dispatch('incrementBy', amount)
  }
}

方法二:使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用

import { mapActions } from 'vuex'

export default {
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

5.Module

解决store过于庞大臃肿的情况,Vuex 允许我们将 store 分割成模块(module)

每个模块拥有自己的 state、mutation、action、getter

注意:最好不要在不同的模块中,写相同的变量和方法名

store/inde.js中
多个模块基本写法
const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
模块内部

mutation中:接收参数(state,rootState)

getter中:接收参数(state, getters, rootState, rootGetters)

action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
  state: () => ({
    count: 0
  }),
  mutations: {
    increment (state) {
      // 这里的 `state` 对象是模块的局部状态,
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  },
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      // state.count是模块内state中的count
      // rootState.count是根节点中state中的count(同名了,尽量不这样写,这里是为了强调模块内状态和根节点状态)
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}
命名空间(可嵌套多层,随意)

以moduleA为例

modules/account.js

export default {
    namespaced: true,// 其成为带命名空间的模块(必须)
    // 模块内容(module assets)
    state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
    getters: {
      //使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入
      isAdmin (state, getters, rootState, rootGetters) { ... } // -> getters['account/isAdmin']
    },
    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'account/someGetter'(account模块中的getters中的方法)
        rootGetters.someGetter // -> 'someGetter'(根节点getters方法)

        dispatch('someOtherAction') // -> 'account/someOtherAction'(account模块中的actions中的方法)
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'(根节点actions中方法)

        commit('someMutation') // -> 'account/someMutation'(account模块中的mutation中的方法)
        commit('someMutation', null, { root: true }) // -> 'someMutation'(根节点mutation中的方法)
      },
    },
    mutations: {
      login () { ... } // -> commit('account/login')
    },
}

==补充:若想在模块中注册全局action:可添加 root: true,并将这个 action 的定义放在函数 handler 中==

actions: {
    someAction: {
       root: true,
       handler (namespacedContext, payload) { ... } // -> 'someAction'
    }
}

store/index.js中

import Vue from 'vue';
import Vuex from 'vuex';
import account from './modules/account'
Vue.use(Vuex);

const store = {
    state: {},
    modules: {
        account
    }
};

export default new Vuex.Store(store);
在 Vue 组件中获得 Vuex 状态

方式一:(较繁琐)

computed: {
  ...mapState({
    a: state => state.account.a,// 这里根据自己嵌套的层数书写
    b: state => state.account.b
  })
},
methods: {
  ...mapActions([
    'state.account/someAction', // -> this['state.account/someAction']()
    'state.account/otherAction' // -> this['state.account/otherAction']()
  ])
}

方式二:(稍微简化)

computed: {
  ...mapState('state.account', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('state.account', [
    'someAction', // -> this.someAction()
    'otherAction' // -> this.otherAction()
  ])
}

vuex数据持久化

vuex存储状态出现的问题:页面刷新后 vuex的数据会丢失,因为页面刷新会重新执行js脚本,数据是存在内存中的,页面刷新后这块空间自然就释放了没有了

数据持久化解决的问题:刷新不再丢失,插件变相把数据存在localStorage中

要求:

配合vuex的module模块化使用

安装插件vuex-persistedstate:npm i vuex-persistedstate或yarn add vuex-persistedstate

项目结构:

├─store                  
│ ├─modules                 
│ ├─├─currentPage.js               
│ ├─plugin              
│ ├─├─PersistedState.js          
│ ├─index.js

currentPage.js

function defaultState() {
    return {
        myInfos: { name: '一瓶人间小样', age: 18 },//用户信息
    }
}

export default {
    namespaced: true,
    state: defaultState(),
    mutations: {
        setMyInfos(state, payload) {
            state.myInfos = payload;
        }
    }
}

PersistedState.js

import createPersistedState from 'vuex-persistedstate';// 导入插件

export default createPersistedState({
    // 保存到 localStorage 的 key 值。已经确定的值,不要改就好了
	key: 'my_persisted_state',
    // 仅将特定的 state 值持久化到本地。例如:'a' => 'state.a' 对应的值持久化到本地。
	paths: [
        'currentPage',
    ]
});

index.js

import Vue from 'vue';
import Vuex from 'vuex';
import PersistedStatePlugin from '@/store/plugin/PersistedState';
import currentPage from './modules/currentPage'
Vue.use(Vuex);

const store = {
    state: {
    },
    mutations: {
    },
    modules: {
        currentPage
    },
	actions: {},
	plugins: [PersistedStatePlugin]
};

export default new Vuex.Store(store);

如图:这里有坑,只有你在某个地方修改了持久化的状态,这里才会给您保存,如果你没有修改过,就不会有 image-20220415095937567.png

vuex-persistedstate使用传送