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中的style.display属性操作
Mar 27 Javascript
包含中国城市的javascript对象实例
Aug 03 Javascript
以jQuery中$.Deferred对象为例讲解promise对象是如何处理异步问题
Nov 13 Javascript
两种简单的跨域方法(jsonp、php)
Jan 02 Javascript
JQuery Ajax 异步操作之动态添加节点功能
May 24 jQuery
微信小程序实现点击按钮修改文字大小功能【附demo源码下载】
Dec 06 Javascript
Vuex提升学习篇
Jan 11 Javascript
ES6关于Promise的用法详解
May 07 Javascript
vue实现点击关注后及时更新列表功能
Jun 26 Javascript
vue之延时刷新实例
Nov 14 Javascript
ES5 模拟 ES6 的 Symbol 实现私有成员功能示例
May 06 Javascript
微信小程序实现身份证取景框拍摄
Sep 09 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
点评山进PR-D3L三波段收音机
2021/03/02 无线电
5款适合PHP使用的HTML编辑器推荐
2015/07/03 PHP
PHP 自动加载的简单实现(推荐)
2016/08/12 PHP
Yii框架函数简单用法分析
2019/09/09 PHP
jquery 单击li防止重复加载的实现代码
2010/12/24 Javascript
JavaScript学习笔记之JS对象
2015/01/22 Javascript
JS获取随机数和时间转换的简单实例
2016/07/10 Javascript
jQuery设置Easyui校验规则(推荐)
2016/11/21 Javascript
利用Vue.js框架实现火车票查询系统(附源码)
2017/02/27 Javascript
强大的JavaScript响应式图表Chartist.js的使用
2017/09/13 Javascript
JavaScript模拟实现封装的三种方式及写法区别
2017/10/27 Javascript
jQuery length 和 size()区别总结
2018/04/26 jQuery
vue超时计算的组件实例代码
2018/07/09 Javascript
vuejs点击class变化的实例
2018/09/05 Javascript
详解关于vue2.0工程发布上线操作步骤
2018/09/27 Javascript
Vue2(三)实现子菜单展开收缩,带动画效果实现方法
2019/04/28 Javascript
Vue Element UI + OSS实现上传文件功能
2019/07/31 Javascript
layui form.render('select', 'test2') 更新渲染的方法
2019/09/27 Javascript
在vue-cli3.0 中使用预处理器 (Sass/Less/Stylus) 配置全局变量操作
2020/08/10 Javascript
python求pi的方法
2014/10/08 Python
操作Windows注册表的简单的Python程序制作教程
2015/04/07 Python
tensorflow学习笔记之简单的神经网络训练和测试
2018/04/15 Python
Python如何将函数值赋给变量
2020/04/28 Python
keras .h5转移动端的.tflite文件实现方式
2020/05/25 Python
Html5获取高德地图定位天气的方法
2019/12/26 HTML / CSS
维多利亚的秘密官方网站:Victoria’s Secret
2018/10/24 全球购物
PHP如何调用MYSQL存储过程
2014/05/30 面试题
C/C++ 笔试、面试题目大汇总
2015/11/21 面试题
什么是ARP(Address Resolution Protocol)地址解析协议
2013/10/31 面试题
Python面试题:Python里面如何生成随机数
2015/03/12 面试题
JAVA软件工程师测试题
2014/07/25 面试题
优秀教师获奖感言
2014/01/31 职场文书
商场收银员岗位职责
2015/04/07 职场文书
2016公司年会主持词
2015/07/01 职场文书
关于战胜挫折的名言警句大全!
2019/07/05 职场文书
html2 canvas svg不能识别的解决方案
2021/06/03 HTML / CSS