Vuex模块化应用实践示例


Posted in Javascript onFebruary 03, 2020

Vuex作为Vue全家桶的成员之一,重要性肯定不用多说,正在做Vue项目的同学,随着项目需求、功能逐渐增加,用到Vuex也是早晚的事儿,作为一个前端,只能面对现实:学不动也得学!

这篇文章主要介绍Vuex在大型项目中的模块化及持久化应用实践,下面正文开始

Vuex的应用场景

  • 多个组件视图共享同一状态时(如登录状态等)
  • 多个组件需要改变同一个状态时
  • 多个组件需要互相传递参数且关系较为复杂,正常传参方式变得难以维护时
  • 持久化存储某些数据

所以我们把组件共享的状态抽离出来,不管组件间的关系如何,都通过Vuex来处理

组织store目录

我们先按模块化的方式组织store目录,并在Vue根实例中注册store,Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中

src
├── ...
├── main.js
├── App.vue
└── store
  ├── index.js     # 我们组装模块并导出 store 的地方
  └── modules
    ├── product.js    # 产品模块
    ├── windowInfo.js  # 窗口信息模块
    └── user.js     # 登录模块

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import product from './modules/product'
import windowInfo from './modules/windowInfo'

Vue.use(Vuex)

export default new Vuex.Store({
 modules: {
  // 注册modules中的模块
  user,
  product,
  windowInfo
 }
})

src/main.js

import ...
import store from './store' // 添加这行

new Vue({
 el: '#app',
 router,
 store, // 注入到根实例
 template: '<App/>',
 components: { App }
})

store的属性

state(状态对象)
state中存放多页面共享的状态字段

getters
相当于当前模块state的计算属性

mutations
如果想更新state中的字段,提交mutations中定义的事件是唯一的方式(key为事件名,value是一个函数),但是这个事件函数必须是同步执行的

actions
可以定义异步函数,并在回调中提交mutation,就相当于异步更新了state中的字段

Vuex模块化应用实践示例

vuex数据传递规则

使用方法

把窗口的高度和宽度存到Vuex中,并且每当窗口被resize,state中的高度和宽度自动更新

src/store/modules/windowInfo.js

import { MU_WIN_RESIZE } from '../../common/constants'
const windowInfo = {
 state: {
  // 初始化
  winHeight: 0,
  winWidth: 0
 },
 mutations: {
  // 这里把事件名统一抽离到constants.js统一管理,方便维护,避免重复。
  // 当然,你也可以不这么写。。
  // mutation事件接受的第一个参数是当前模块的state对象
  // 第二个参数是提交事件时传递的附加参数
  [MU_WIN_RESIZE] (state, payload) {
   const { winWidth, winHeight } = payload
   state.winWidth = winWidth
   state.winHeight = winHeight
  }
 },
 actions: {},
 getters: {}
}

export default windowInfo

src/common/constants.js

export const MU_WIN_RESIZE = 'MU_WIN_RESIZE' // 更新窗口尺寸

下面打开项目的根组件添加监听resize事件和提交mutation事件逻辑

src/App.vue

<!--上面的template我就不往这儿放了-->
<script>
 import { _on, _off, getClientWidth, getClientHeight } from './common/dom'
 import { MU_WIN_RESIZE } from './common/constants'
 import { mapMutations } from 'vuex'

 export default {
  name: 'app',
  data () {
   return {}
  },
  mounted () {
   this.handleResize()
   // 这里对addEventListener方法做了IE兼容处理,就不贴出来了,反正事件监听你们都会
   _on(window, 'resize', this.handleResize)
  },
  beforeDestroy () {
   _off(window, 'resize', this.handleResize)
  },
  methods: {
   // 对象展开运算符,不熟悉的同学该学学ES6了
   ...mapMutations({
    // 映射 this.winResize 为 this.$store.commit(MU_WIN_RESIZE)
    winResize: MU_WIN_RESIZE
   }),
   handleResize () {
    const winWidth = getClientWidth()
    const winHeight = getClientHeight()
    this.winResize({ winWidth, winHeight })
   }
  }
 }
</script>

到这一步,在拖动窗口触发‘resize'事件的时候,就会触发‘MU_WIN_RESIZE'这个mutation事件并把窗口宽高写入vuex,下面我们随便找个页面看能不能获取到我们写入的值

<template>
 <div class="row">窗口高:{{winHeight}} 窗口宽:{{winWidth}}</div>
</template>
<script>
 import { mapState } from 'vuex'
 export default {
  name: 'test',
  data () {
   return {}
  },
  computed: {
   // 把state写入计算属性
   // 如果使用mapGetters也是写入计算属性
   ...mapState({
    winHeight: state => state.windowInfo.winHeight,
    winWidth: state => state.windowInfo.winWidth
   })
  },
 }
</script>

有的时候我们会从后端获取一些下拉框选项的静态常量,而且很多页面都能用到,这个时候用Vuex是比较好的选择,涉及到后端获取,就要用到可以使用异步的actions了

src/store/modules/product.js

import {MU_PRODUCT_UPDATE_CONSTANTS} from '../../common/constants'

const product = {
 state: {
  productConstants: []
 },
 mutations: {
  [MU_PRODUCT_UPDATE_CONSTANTS] (state, payload) {
   state.productConstants = payload
  }
 },
 actions: {
  // action函数第一个参数接受一个与 store 实例具有相同方法和属性的 context 对象,
  // 因此你可以调用 context.commit 提交一个 mutation,
  // 或者通过 context.state 和 context.getters 来获取 state 和 getters
  // 这里虽然能获取到state,但是不建议直接修改state中的字段
  async getProductConstants ({ commit }, payload) {
   try {
    // 请求接口,如果需要参数可以通过payload传递
    const res = await this.$api.product.getConstants()
    commit(MU_PRODUCT_UPDATE_CONSTANTS, res)
   } catch (e) {
    console.error(e)
   }
  }
 },
 getters: {}
}

export default product

下面触发这个getProductConstants事件,触发这个action事件的位置需要注意一下,假设你有5个组件需要使用这个state,那就应该在这5个组件共同的父组件中调用一次action(找不到就在根实例中调用),然后在各个子组件中通过mapState或mapGetters获取state,千万不要每个组件使用前都调用一次action方法!

src/App.vue

<!--为了更直观的展示action,把之前的代码删掉了-->
<script>
 import { mapActions } from 'vuex' // 注意是mapActions

 export default {
  name: 'app',
  data () {
   return {}
  },
  created () {
   // 触发请求
   this.getProductConstants()
  }
  methods: {
   ...mapActions([
    // 映射 this.getProductConstants 为 this.$store.dispatch('getProductConstants')
    'getProductConstants'
   ])
  }
 }
</script>

mapGetters, mapMutations, mapActions,这几个函数可以接受对象也可以接受数组作为参数,如果你需要在组件中以别的名字调用该事件(像上面的mapMutations)就可以传入对象,key为新命名,value是store中定义的名字;否则的话传数组就好了。

那么问题来了,既然是异步操作,我想在操作结束后干点儿别的怎么做呢?
很简单,调用action中的异步函数(this.$store.dispatch)返回的是一个Promise,如果你跟我一样用的是async await:

<!--为了更直观的展示action,把之前的代码删掉了-->
<script>
 import { mapActions } from 'vuex' // 注意是mapActions

 export default {
  name: 'app',
  data () {
   return {}
  },
  async created () {
   // 触发请求
   await this.getProductConstants()
   // 接下来执行的操作会等待上面函数完成才会执行
  }
  methods: {
   ...mapActions([
    // 映射 this.getProductConstants 为 this.$store.dispatch('getProductConstants')
    'getProductConstants'
   ])
  }
 }
</script>

如果你用的不是async await那就麻烦一点,在actions中定义事件的时候return一个new Promise,官方文档中有一个例子

表单处理

当你把从state中获取的字段填在v-model中时,如果用户修改表单数据,v-model会尝试直接修改store中的数据,这样做会有两个问题:

  1. 破坏了vuex的数据传递规则,如果想修改state中的数据只能通过提交一个mutation
  2. 控制台报错:计算属性没有setter

官方提供了两种解决方法,我更倾向于下面这种,给计算属性添加setter,并在setter中提交mutation修改state:

<template>
 <input v-model="message">
</template>
<script>
  export default {
  name: 'app',
  data () {
   return {}
  },
  computed: {
   message: {
    get () {
     return this.$store.state.test.message
    },
    set (value) {
     this.$store.commit('updateMessage', value)
    }
   }
  }
  methods: {}
 }
</script>

Vuex持久化

推荐插件vuex-persist

安装插件:

npm install --save vuex-persist

引入、配置、加载插件:
src/store/persist.js

import VuexPersistence from 'vuex-persist'

const persist = new VuexPersistence({
 // 其他参数看文档
 storage: window.sessionStorage
})
export default persist.plugin

src/store/index.js

import ...
import persist from './persist'

Vue.use(Vuex)

export default new Vuex.Store({
 modules: {
  user,
  product,
  windowInfo
 },
 plugins: [persist]
})

现在刷新浏览器数据也不会重置了!

总结

以上就是vuex比较常规的操作了,第一次看官方文档的我是懵逼的、无助的,但是用了一段时间vuex再重新看文档的时候会有很多收获。希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
短信提示使用 特效
Jan 19 Javascript
JavaScript 拾碎[三] 使用className属性
Oct 16 Javascript
javascript结合CSS实现苹果开关按钮特效
Apr 07 Javascript
javascript中FOREACH数组方法使用示例
Mar 01 Javascript
javascript正则表达式中分组详解
Jul 17 Javascript
js代码延迟一定时间后执行一个函数的实例
Feb 15 Javascript
vue进行图片的预加载watch用法实例讲解
Feb 07 Javascript
JavaScript常用截取字符串的三种方式用法区别实例解析
May 15 Javascript
axios 封装上传文件的请求方法
Sep 26 Javascript
快速搭建Node.js(Express)用户注册、登录以及授权的方法
May 09 Javascript
小程序自定义弹框效果
Nov 16 Javascript
vue使用require.context实现动态注册路由
Dec 25 Vue.js
微信小程序8种数据通信的方式小结
Feb 03 #Javascript
使用js实现单链解决前端队列问题的方法
Feb 03 #Javascript
javscript 数组扁平化的实现
Feb 03 #Javascript
Vue强制组件重新渲染的方法讨论
Feb 03 #Javascript
JavaScript中的类型检查
Feb 03 #Javascript
Vue的Eslint配置文件eslintrc.js说明与规则介绍
Feb 03 #Javascript
压缩Vue.js打包后的体积方法总结(Vue.js打包后体积过大问题)
Feb 03 #Javascript
You might like
PHP5 安装方法
2007/01/15 PHP
PHP批量删除、清除UTF-8文件BOM头的代码实例
2014/04/14 PHP
PHP使用pcntl_fork实现多进程下载图片的方法
2014/12/16 PHP
php输出含有“#”字符串的方法
2017/01/18 PHP
CutePsWheel javascript libary 控制输入文本框为可使用滚轮控制的js库
2010/02/07 Javascript
js 数值项目的格式化函数代码
2010/05/14 Javascript
JQuery插件Style定制化方法的分析与比较
2012/05/03 Javascript
浅析IE10兼容性问题(frameset的cols属性)
2014/01/03 Javascript
jQuery+PHP+MySQL二级联动下拉菜单实例讲解
2015/10/27 Javascript
js密码强度检测
2016/01/07 Javascript
Vue服务端渲染和Vue浏览器端渲染的性能对比(实例PK )
2017/03/31 Javascript
Bootstrap table使用方法记录
2017/08/23 Javascript
JS实现遍历不规则多维数组的方法
2018/03/21 Javascript
微信小程序实现tab左右切换效果
2020/11/15 Javascript
webpack4 SplitChunks实现代码分隔详解
2019/05/23 Javascript
关于vue里页面的缓存详解
2019/11/04 Javascript
vue项目里面引用svg文件并给svg里面的元素赋值
2020/08/17 Javascript
Vue项目打包编译优化方案
2020/09/16 Javascript
如何利用node转发请求详解
2020/09/17 Javascript
Javascript confirm多种使用方法解析
2020/09/25 Javascript
[00:33]2016完美“圣”典风云人物:BurNIng宣传片
2016/12/10 DOTA
python 重定向获取真实url的方法
2018/05/11 Python
如何在django里上传csv文件并进行入库处理的方法
2019/01/02 Python
PyTorch实现ResNet50、ResNet101和ResNet152示例
2020/01/14 Python
Python enumerate内置库用法解析
2020/02/24 Python
2014年圣诞节倒计时网页的制作过程
2014/12/05 HTML / CSS
清除canvas画布内容(点擦除+线擦除)
2020/08/12 HTML / CSS
Windows和Linux动态库应用异同
2016/04/17 面试题
大学毕业寄语大全
2014/04/10 职场文书
教师见习期自我鉴定
2014/04/28 职场文书
《中国梦我的梦》中学生演讲稿
2014/08/20 职场文书
领导四风问题整改措施思想汇报
2014/10/13 职场文书
音乐教师个人工作总结
2015/02/06 职场文书
党支部对转正的意见
2015/06/02 职场文书
2015年街道办事处团委工作总结
2015/10/14 职场文书
小学四年级作文之写景
2019/08/23 职场文书