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 相关文章推荐
豆瓣网的jquery代码实例
Jun 15 Javascript
innerhtml用法 innertext用法 以及innerHTML与innertext的区别
Oct 26 Javascript
js优化针对IE6.0起作用(详细整理)
Dec 25 Javascript
JS实现样式清新的横排下拉菜单效果
Oct 09 Javascript
JS实现的倒计时效果实例(2则实例)
Dec 23 Javascript
seajs模块压缩问题与解决方法实例分析
Oct 10 Javascript
Vue-resource拦截器判断token失效跳转的实例
Oct 27 Javascript
jQuery 防止相同的事件快速重复触发方法
Feb 08 jQuery
重新认识vue之事件阻止冒泡的实现
Aug 02 Javascript
jQuery实现动态添加和删除input框代码实例
Mar 29 jQuery
nuxt框架中对vuex进行模块化设置的实现方法
Sep 06 Javascript
vue自定义树状结构图的实现方法
Oct 18 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
FirePHP 推荐一款PHP调试工具
2011/04/23 PHP
访问编码后的中文URL返回404错误的解决方法
2014/08/20 PHP
浅谈PHP中的
2016/04/23 PHP
JavaScript 设计模式学习 Factory
2009/07/29 Javascript
jquery 页面全选框实践代码
2010/04/02 Javascript
javascript 隔行换色函数代码
2010/10/24 Javascript
Web Inspector:关于在 Sublime Text 中调试Js的介绍
2013/04/18 Javascript
javascript制作的网页侧边弹出框思路及实现代码
2014/05/21 Javascript
JS中的Replace方法使用经验分享
2015/05/20 Javascript
使用HTML+CSS+JS制作简单的网页菜单界面
2015/07/27 Javascript
js 动态给元素添加、移除事件的实现方法
2016/07/19 Javascript
老生常谈jquery中detach()和remove()的区别
2017/03/02 Javascript
详解vue2.0组件通信各种情况总结与实例分析
2017/03/22 Javascript
Vue.js实现模拟微信朋友圈开发demo
2017/04/20 Javascript
JS库 Highlightjs 添加代码行号的实现代码
2017/09/13 Javascript
利用10行js代码实现上下滚动公告效果
2017/12/08 Javascript
nodejs实现连接mongodb数据库的方法示例
2018/03/15 NodeJs
vue动态绑定组件子父组件多表单验证功能的实现代码
2018/05/14 Javascript
npm 下载指定版本的组件方法
2018/05/17 Javascript
vue 百度地图(vue-baidu-map)绘制方向箭头折线实例代码详解
2020/04/28 Javascript
修改Vue打包后的默认文件名操作
2020/08/12 Javascript
[00:52]DOTA2国际邀请赛
2020/02/21 DOTA
python实现登陆知乎获得个人收藏并保存为word文件
2015/03/16 Python
flask中使用蓝图将路由分开写在不同文件实例解析
2018/01/19 Python
pyqt5 使用cv2 显示图片,摄像头的实例
2019/06/27 Python
Python 安装 virturalenv 虚拟环境的教程详解
2020/02/21 Python
CSS3教程(1):什么是CSS3
2009/04/02 HTML / CSS
.net面试题
2016/09/17 面试题
shell变量的作用空间是什么
2013/08/17 面试题
航海技术专业毕业生求职信
2014/04/06 职场文书
小学运动会演讲稿
2014/08/25 职场文书
合同纠纷调解书
2015/05/20 职场文书
三八妇女节主持词
2015/07/04 职场文书
关于环保的宣传稿
2015/07/23 职场文书
宣传稿格式范文
2015/07/23 职场文书
Java实现二维数组和稀疏数组之间的转换
2021/06/27 Java/Android