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 相关文章推荐
用JS剩余字数计算的代码
Jul 03 Javascript
Flexigrid在IE下不显示数据的处理的解决方法
Oct 24 Javascript
javascripit实现密码强度检测代码分享
Dec 12 Javascript
jQuery学习笔记之 Ajax操作篇(二) - 数据传递
Jun 23 Javascript
js用typeof方法判断undefined类型
Jul 15 Javascript
如何解决谷歌浏览器下jquery无法获取图片的尺寸
Sep 10 Javascript
深入理解JavaScript内置函数
Jun 03 Javascript
js实现带三角符的手风琴效果
Mar 01 Javascript
JavaScrpt中如何使用 cookie 设置查看与删除功能
Jul 09 Javascript
JavaScript实现的浏览器下载文件的方法
Aug 09 Javascript
js实现烟花特效
Mar 02 Javascript
在vue项目中promise解决回调地狱和并发请求的问题
Nov 09 Javascript
微信小程序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
Amazon Prime Video平台《无限住人 -IMMORTAL-》2020年开始TV放送!
2020/03/06 日漫
php生成缩略图填充白边(等比缩略图方案)
2013/12/25 PHP
跟我学Laravel之配置Laravel
2014/10/15 PHP
PHP读取文件内容的五种方式
2015/12/28 PHP
详解PHP实现异步调用的4种方法
2016/03/14 PHP
PHP框架性能测试报告
2016/05/08 PHP
tp5.1 实现setInc字段自动加1
2019/10/18 PHP
ThinkPHP5.0框架实现切换数据库的方法分析
2019/10/30 PHP
JavaScript 弹出窗体点击按钮返回选择数据的实现
2010/04/01 Javascript
JQuery优缺点分析说明
2011/04/10 Javascript
Jquery修改image的src属性,图片不加载问题的解决方法
2016/05/17 Javascript
js选择器全面解析
2016/06/27 Javascript
AngularJs自定义服务之实现签名和加密
2016/08/02 Javascript
javascript稀疏数组(sparse array)和密集数组用法分析
2016/12/28 Javascript
webpack热模块替换(HMR)/热更新的方法
2018/04/05 Javascript
nodejs读取并去重excel文件
2018/04/22 NodeJs
详解Vue的钩子函数(路由导航守卫、keep-alive、生命周期钩子)
2018/07/24 Javascript
详解微信小程序调起键盘性能优化
2018/07/24 Javascript
layui字体图标 loading图标静止不旋转的解决方法
2019/09/23 Javascript
[02:54]DOTA2英雄基础教程 暗影牧师戴泽
2013/12/05 DOTA
[14:57]DOTA2 HEROS教学视频教你分分钟做大人-幽鬼
2014/06/13 DOTA
python统计文本字符串里单词出现频率的方法
2015/05/26 Python
Python的几个高级语法概念浅析(lambda表达式闭包装饰器)
2016/05/28 Python
Python实现的寻找前5个默尼森数算法示例
2018/03/25 Python
python读取文件名并改名字的实例
2019/01/07 Python
详解django2中关于时间处理策略
2019/03/06 Python
python文件选择对话框的操作方法
2019/06/27 Python
Python字典fromkeys()方法使用代码实例
2020/07/20 Python
flask框架中的cookie和session使用
2021/01/31 Python
html5 分层屏幕适配的方法
2018/03/16 HTML / CSS
大客户销售经理职责
2013/12/04 职场文书
秘书专业自荐信范文
2013/12/26 职场文书
经典导游欢迎词大全
2014/01/16 职场文书
迎新晚会策划方案
2014/06/13 职场文书
优秀应届毕业生自荐书
2014/06/29 职场文书
mysql主从复制的实现步骤
2021/10/24 MySQL