vuex的使用及持久化state的方式详解


Posted in Javascript onJanuary 23, 2018

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

当我们接触vuex的时候,这是我们最先看到的一句官方引导。

从这句话中,我们可以得到如下几个信息:

1、vuex是一个为vue而存在的特化的Flux,如同数据库中的弱实体一样,离开了vue,vuex就用不了。反之可以看到redux就不存在,无论是vue还是react,redux都可以使用。所以这里体现的vuex的“特性”,redux则具备“普适性”

2、集中式的管理说明vue中所有的组件的状态都是存在于vuex中

3、使用vuex你就要遵循我的规则,这样组件中状态的变化我才能跟踪的到。

1.项目中vuex目录的搭建

vuex的使用及持久化state的方式详解

上图是我在这篇文章中,vue整体项目骨架的局部。

vuex使用的是单一的状态树,我们的vue应用将仅仅包含一个 store 的实例。所以当我们将store挂载到vue的实例上以后,我们可以通过this.$store取到vuex里面的各个部分。

2.index.js

import vue from 'vue'
import vuex from 'vuex'
import mutations from './mutation'
import getters from './getter'

vue.use(vuex)

const state = {
 isLoading:false
}

export default new vuex.Store({
 state,
 getters,
 mutations
})

在index这个文件中,我们会去定义我们需要在vuex中存储的状态初始值。

比如说,我在上面的state对象中去存储了一个isLoading属性,该属性我准备用它来标识我请求backend API的时候显示,在请求完成后消失的这样一个loading的效果,来缓解一下用户的等待心理。

3.Mutation(mutation.js)

一般来说,我们在项目中最常用的就是mutation.js里面的方法了。因为更改vuex中的store里的state的唯一的方式就是提交mutation。

在vuex中,每个mutation都有一个字符串的事件类型(mutation-type)和一个回调函数(handler)。

这个回调函数可接受两个参数,第一个参数为state,第二参数是mutation的载荷(payload)。

//...
mutations: {
 /**
 * @method:只传入state,修改loading状态
 * @param {bool} isLoading:loading状态
 */ 
 changeLoading(state) {
 state.isLoading = !state.isLoading
 }
}
store.commit('changeLoading')

mutations: {
 /**
 * @method:传入state和payload,修改loading状态
 * @param {bool} isLoading:loading状态
 */ 
 changeLoading(state,payload) {
 state.isLoading = payload.isLoading
 }
}
store.commit('changeLoading',{isLoading: true})

还有一种提交mutation的方式是直接使用包含 type 属性的对象,不过我不是很推荐这样的方式,因为用上面的方式来处理的话,代码的易读性会更高。

store.commit({
 type: 'changeLoading',
 isLoading: true
})

4.mutation-types.js

在需要多人协作的项目中,我们可以使用常量代替mutation 事件类型。这在各种 Flux 实现中是很常见的模式。同时把这些常量放在单独的文件中可以让协作开发变得清晰。

// mutation-types.js
export const CHANGE_LOADING= 'CHANGE_LOADING'

// mutation.js
import { CHANGE_LOADING} from './mutation-types'

export default{
 [CHANGE_LOADING](state,payload){
  state.isLoading = payload.isLoading
 },
}

对于定义mutation-type里面的事件类型,我大致遵循我自己定义的如下规范:

1、因为mutation类似于事件,所以以动词开头

2、单词间以下划线进行连接

3、保存到vuex里面的状态用RECORD标识

4、缓存到本地的状态用SAVE标识

 当然,这个规范的话大家可以自己定义,只要能通过mutation-type就能知道mutation的意图就是极好的。

5.Getter(getter.js)

有时候我们需要从 store 中的 state 中派生出一些状态,例如我上面提到的需要在异步请求的时候去显示一个带有遮罩层的loading,然后我loading的下面需要根据state去展示loading的状态。在不使用getter的情况下,我们会选择使用计算属性去处理。

computed: {
 loadingTxt () {
 return this.$store.state.isLoading ? '加载中' : '已完成';
 }
}

但是,我们这个loading需要在很多的组件中去使用它。那么,我们要么复制这个函数,要么抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

如果使用Getter,那么一切都变得美好了。

//getter.js
export default {
 loadingTxt:(state) =>{
  return state.isLoading ? '加载中' : '已完成';
 } 
};

就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

并且,Getter 也可以接受其他 getter 作为第二个参数:

export default {
 loadingTxt:(state) =>{
  return state.isLoading ? '加载中' : '已完成';
 },
  isLoading:(state,getters) => {
  return 'string' === typeof getters.loadingTxt ? true : false;
 } 
};

通过mapGetters辅助函数可以将 store 中的 getter 映射到局部计算属性

//组件中
import { mapGetters } from 'vuex'

export default {
 data(){
 return {
  //...
 }
 },
 computed: {
 // 使用对象展开运算符将 getter 混入 computed 对象中
 ...mapGetters([
  'loadingTxt',
  'isLoading',
  // ...
 ])
 }
}

6.Action(action.js)

action的功能和mutation是类似的,都是去变更store里的state,不过action和mutation有两点不同:

1、action主要处理的是异步的操作,mutation必须同步执行,而action就不受这样的限制,也就是说action中我们既可以处理同步,也可以处理异步的操作

2、action改变状态,最后是通过提交mutation

就拿购物车来说,当我们去添加一个商品的时候,我们需要先和后台去通讯一次,这里涉及到sku或者说是如果用户只添加了但是没有去下单。

如果后台添加成功,前端再去展示新添加的商品,如果失败,我们需要告诉用户添加失败了。

const actions = {
 checkout ({ 
   state,


commit,
   //rootState 



}, products) {
 const savedCartItems = [...state.added]
 commit(SET_CHECKOUT_STATUS, null)
 // 置空购物车
 commit(SET_CART_ITEMS, { items: [] })
 shop.buyProducts(
  products,

 //成功
  () => commit(SET_CHECKOUT_STATUS, 'successful'),
  //失败
  () => {
  commit(SET_CHECKOUT_STATUS, 'failed')
  commit(SET_CART_ITEMS, { items: savedCartItems })
  }
 )
 }
}

7.module

当我们的项目足够大的时候,单一的状态树这个时候就会显得很臃肿了。因为需要用vuex进行状态管理的状态全部集中在一个state对象里面。

所以,当一个东西大了以后,我们就要想办法进行分割,同样的道理,我们熟知的分冶法和分布式其实也是基于这样的一个思想在里面。而vuex提供了module,我们就可以去横向的分割我们的store。

比如说,我在项目中需要去做一个购物车这样的东西,这在电商的项目中也是常见的需求。

//shopCart.js
import shop from '../../api/shop'
import {
 ADD_TO_CART,
 SET_CART_ITEMS,
 SET_CHECKOUT_STATUS
} from '../mutation-types'

const state = {
 added: [],
 checkoutStatus: null
}

/**
 * module getters
 * @param {Object} state:模块局部state
 * @param {Object} getters:模块局部getters,会暴露到全局
 * @param {Object} rootState:全局(根)state
 */
const getters = {
 checkoutStatus: state => state.checkoutStatus,
 cartProducts: (state, getters, rootState) => {
 return state.added.map(({ id, quantity }) => {
  const product = rootState.products.all.find(product => product.id === id)
  return {
  title: product.title,
  price: product.price,
  quantity
  }
 })
 },
 cartTotalPrice: (state, getters) => {
 return getters.cartProducts.reduce((total, product) => {
  return total + product.price * product.quantity
 }, 0)
 }
}

/**
 * module actions
 * @param {Object} state:模块局部state
 * @param {Object} getters:模块局部getters,会暴露到全局
 * @param {Object} rootState:全局(根)state
 */
const actions = {
 checkout ({ 
   state,


commit,
   //rootState 



}, products) {
 const savedCartItems = [...state.added]
 commit(SET_CHECKOUT_STATUS, null)
 // 置空购物车
 commit(SET_CART_ITEMS, { items: [] })
 shop.buyProducts(
  products,

 //成功
  () => commit(SET_CHECKOUT_STATUS, 'successful'),
  //失败
  () => {
  commit(SET_CHECKOUT_STATUS, 'failed')
  commit(SET_CART_ITEMS, { items: savedCartItems })
  }
 )
 }
}

/**
 * module mutations
 * @param {Object} state:模块局部state
 * @param payload:mutation的载荷
 */
const mutations = {
 //用id去查找商品是否已存在,
 [ADD_TO_CART] (state, { id }) {
 state.checkoutStatus = null
 const record = state.added.find(product => product.id === id)
 if (!record) {
  state.added.push({
  id,
  quantity: 1
  })
 } else {
  record.quantity++
 }
 },
 [SET_CART_ITEMS] (state, { items }) {
 state.added = items
 },
 [SET_CHECKOUT_STATUS] (state, status) {
 state.checkoutStatus = status
 }
}

export default {
 state,
 getters,
 actions,
 mutations
}

在module的定义的局部state,getters,mutation,action中,后三个都会暴露到全局的store中去,这样使得多个模块能够对同一 mutation 或 action 作出响应。就不需要在其他的模块中去定义相同的mutation或action了。

而这里的state是局部的。这也导致后来的持久化无法去处理用module分割后的state。

如同上面的module =》shopCart,

当我们无论是在index.js里面或者其他的module中,shopCart里面的getters或者action或者mutations,我们都可以去使用。

//test.js
const state = {
 isTest:false
};

const getters = {
 isTest :state => state.isTest,
 checkTestStatus:(state,getters) => {
 return getters.checkoutStatus;
 }
};

export default {
 state,
 getters,
}
//组件中
...mapGetters([
 'checkTestStatus'
])
//...
created(){
 this.checkTestStatus ;//null
}

如果说,我就想让我的module里面的定义的全部都是独享的。我们可以使用module的命名空间,通过设置namespaced: true。

//test.js
const getters = {
 // 在这个模块的 getter 中,`getters` 被局部化了
 // 你可以使用 getter 的第四个参数来调用 `rootGetters`
 someGetter (state, getters, rootState, rootGetters) {
 getters.someOtherGetter // 'test/someOtherGetter'
 rootGetters.someOtherGetter // 'someOtherGetter'
 },
 someOtherGetter: state => { ... }
};

const actions = {
 // 在这个模块中, dispatch 和 commit 也被局部化了
 // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
 someAction ({ dispatch, commit, getters, rootGetters }) {
 getters.someGetter // 'test/someGetter'
 rootGetters.someGetter // 'someGetter'

 dispatch('someOtherAction') // 'test/someOtherAction'
 dispatch('someOtherAction', null, { root: true }) // 'someOtherAction'

 commit('someMutation') // 'test/someMutation'
 commit('someMutation', null, { root: true }) // 'someMutation'
 },
 someOtherAction ({ state,commit }, payload) { ... }
}

export default {
 namespaced: true,
 state,
 getters,
 actions,
 mutations
}

8.持久化state的工具:vuex-persistedstate

用过vuex的肯定会有这样一个痛点,就是刷新以后vuex里面存储的state就会被浏览器释放掉,因为我们的state都是存储在内存中的。

而像登录状态这样的东西,你不可能一刷新就让用户重新去登录吧!所以,我们会去选择把状态存储到本地。

这样一来问题貌似是解决了,但是当我们需要使用的时候,我们就需要不断的从本地,通过getStore这样的方法去取得我们state。如果需要更新的话,我们又要在mutation里面通过setStore这样的方法去处理它。

虽然,我们的setStore都是在操作了state以后再去调用的,也就是说无论是通过vuex的logger或者vue的dev tool我们都是可以对local里面的状态进行跟踪的,但是,我们无法保证我们每次都记着去写setStore。

这样一来,在共享state的组件中,我们的代码可能就会是这样的。

import { getStore } from '@/util'
//组件中
mounted(){
 this.foo = getStore('foo');
 this.bar = getStore('bar');
 //.... 
}

那么,如何去改进呢?

我们能想到的就是,能不能让state不是保存在内存中,而是存储在本地。

而vuex-persistedstate做了这样的事情,它帮我们将store里面的state映射到了本地环境中。这样一来,我通过提交mutation改变的state,会动态的去更新local里面对应的值。

大家感兴趣的话,可以戳这里,这里有个小dmeo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript高级程序设计 阅读笔记(二十) js错误处理
Aug 14 Javascript
解决js数据包含加号+通过ajax传到后台时出现连接错误
Aug 01 Javascript
jQuery实现菜单式图片滑动切换
Mar 14 Javascript
基于javascript实现九九乘法表
Mar 27 Javascript
移动端日期插件Mobiscroll.js使用详解
Dec 19 Javascript
微信小程序 省市区选择器实例详解(附源码下载)
Jan 05 Javascript
jQuery表格的维护和删除操作
Feb 03 Javascript
详解AngularJs HTTP响应拦截器实现登陆、权限校验
Apr 11 Javascript
微信小程序 实现列表项滑动显示删除按钮的功能
Apr 13 Javascript
vue移动端实现下拉刷新
Apr 22 Javascript
微信小程序实现弹出菜单功能
Jun 12 Javascript
webpack4.x开发环境配置详解
Aug 04 Javascript
jquery 输入框查找关键字并提亮颜色的实例代码
Jan 23 #jQuery
js 实现复选框只能选择一项的示例代码
Jan 23 #Javascript
Vue 换肤的示例实践
Jan 23 #Javascript
js和jQuery以及easyui实现对下拉框的指定赋值方法
Jan 23 #jQuery
Vue 拦截器对token过期处理方法
Jan 23 #Javascript
浅谈React + Webpack 构建打包优化
Jan 23 #Javascript
vue组件编写之todolist组件实例详解
Jan 22 #Javascript
You might like
关于访问控制的一首PHP面试题(对属性或方法的访问控制)
2012/09/13 PHP
PHP面向对象教程之自定义类
2014/06/10 PHP
浅谈PHP中的面向对象OOP中的魔术方法
2017/06/12 PHP
js 输出内容到新窗口具体实现代码
2013/05/31 Javascript
jQuery客户端分页实例代码
2013/11/18 Javascript
jquery事件重复绑定的快速解决方法
2014/01/03 Javascript
javascript嵌套函数和在函数内调用外部函数的区别分析
2016/01/31 Javascript
jQuery滚动插件scrollable.js用法分析
2017/05/25 jQuery
bootstrap-table组合表头的实现方法
2017/09/07 Javascript
bootstrap模态框嵌套、tabindex属性、去除阴影的示例代码
2017/10/17 Javascript
解决vue 中 echart 在子组件中只显示一次的问题
2018/08/07 Javascript
微信小程序实现动态获取元素宽高的方法分析
2018/12/10 Javascript
VuePress 静态网站生成方法步骤
2019/02/14 Javascript
Node.js 实现远程桌面监控的方法步骤
2019/07/02 Javascript
基于vue实现简易打地鼠游戏
2020/08/21 Javascript
Vue自定义多选组件使用详解
2020/09/08 Javascript
[36:52]DOTA2真视界:基辅特锦赛总决赛
2017/05/21 DOTA
python基础教程之Hello World!
2014/08/29 Python
Windows下PyMongo下载及安装教程
2015/04/27 Python
django定期执行任务(实例讲解)
2017/11/03 Python
解决python3中解压zip文件是文件名乱码的问题
2018/03/22 Python
python 日志增量抓取实现方法
2018/04/28 Python
Python的对象传递与Copy函数使用详解
2019/12/26 Python
浅谈tensorflow 中tf.concat()的使用
2020/02/07 Python
使用css3绘制出各种几何图形
2016/08/17 HTML / CSS
让IE可以变相支持CSS3选择器
2010/01/21 HTML / CSS
在线吉他课程,学习如何弹吉他:Fender Play
2019/02/28 全球购物
可以在一个PHP文件里面include另外一个PHP文件两次吗
2015/05/22 面试题
杭州时比特电子有限公司SQL
2013/08/22 面试题
介绍一下内联、左联、右联
2013/12/31 面试题
家电业务员岗位职责
2014/03/10 职场文书
运动会口号8字
2014/06/07 职场文书
放飞梦想演讲稿200字
2014/08/26 职场文书
学生会辞职信
2015/03/02 职场文书
解析Java中的static关键字
2021/06/14 Java/Android
Appium中scroll和drag_and_drop根据元素位置滑动
2022/02/15 Python