vuex

2,852 阅读11分钟

编写时间:2019-07-31
更新时间:2019-09-21

作者:鬼小妞

目的:

备注: 本文编写了与vuex状态管理相关的一些知识,仅供参考,描述不当的地方,请评论指正,在这篇文章中,我将先总结vuex常用属性,然后以简单的demo来一步步带你熟悉使用vuex流程,熟悉操作流程后再一步步详解个属性及属性的相关内容,特别介绍electron项目中遇到的vuex的问题

状态:待整理、待编写、待更新2019-08-07

[TOC]

状态管理示意图

  • 约定所有state都必续通过mutations来改变,目的:开发工具可以追踪、调试修改状态

  • electron项目中,请在render进程中调用dispatch或mapActions。不要使用commit,因为通过commit触发的操作不会在进程之间共享,所以electron-vue项目中,尽量不要在组件里直接commit提交修改请求到mutations。

  • state可以不通过mutations来改变,但开发工具无法追踪

  • 组件中可以越过actions,直接提交修改请求到mutations

  • 组件中使用commit提交请求到mutation时,vue可能会报错,这是vue希望你遵守约定,先把请求dispatch到actions,再由actions提交修改动作到mutations

  • actions存在的意义是:mutations中只能处理同步操作,actions可以处理同步和异步,actions异步操作完成后,异步状态变为同步,再把同步修改state的修改动作提交的mutations处理

  • 使用commit和dispatch时,传递参数是通过该commit、dispatch函数传参数,例如:

    • this.$store.commit("addIncrement",{age:18,n:5},{ root: true })
    • this.$store.dispatch("addIncrement",{age:18,n:5},{ root: true })
  • 使用辅助函数mapState()、mapGetters()等传递参数是通过触发方法传递的

    例1 mapMutations


...mapMutations([
   'increment' // 将this.increment() 映射为 this.$store.commit('increment', payload)
   // 在事件或方法中直接调用 this.increment({amount: 10 })即可
]),


(1)在调用的标签方法里直接传递参数

<div @click="increment({amount: 10 })">Click me</div>

(2)在方法中直接传参this.increment({amount: 10 })

someFn(){
    this.increment({amount: 10 })
}

state、getters、actions、mutations对比

属性对比

其中,在modules模块中,state可能是函数state (){},请看api文档查阅模块重用详情

属性 属性描述 辅助函数 实例对象
state 状态对象 mapState() state = {...} 或 state (){return{}}
getters 包含多个getter计算属性函数的对象 mapGetters() getters = {...}
actions 包含多个对应事件回调函数的对象 mapActions() actions = {...}
mutations 包含多个更新state函数的对象 mapMutations() mutations = {...}

实例对象类型对比

待更新

  • state={存放对象或函数}、 state(){return{存放对象或函数}}

对于官方API文档上列举出来的例如:

初学者不知道这个是什么意思,简单直接就是这些vuex属性{}里存放的类型

  • 特别要注意,state里其实是可以写函数的,这个我在后面的章节中会说,这里暂时不介绍

实例参数对比

此表格待更新!!!

  • actions里的方法对象的参数只有2个

dispatch和commit

vue实例与vuex实例主要属性类比

vuex实例 vue实例
state data
getters computed
actions method
mutations method

简单实例

为了清楚演示vuex流程,我将以一个demo项目来解释vuex的工作流程, 该demo项目是涉及到webpack的一些东西,如果你已经具备webpack知识,然后跳转直接看vuex流程

以下步骤均针对没有webpack基础的读者而进行的详细操作

demo项目准备工作

1. 新建空文件夹,用来存放工程文件

在任一磁盘建立空文件夹,命名为demoproject, 我的项目文件夹路径为E:\aproject\demoproject

2.根据模板创建vue webpack项目

(1)、请先全局安装vue-cli脚手架

npm install -g vue-cli

(2)、右键git base here调出命令窗口,输入以下命令并回车:

vue init webpack testproject

(3)、根据提示完成配置

以下是我的配置,标红的是手动输入n回车的,其他项默认回车

3.安装依赖并安装vuex

(1)、cd到testproject

cd testproject

(2)、安装依赖

npm install

(3)、安装vuex

npm install --save vuex

4.启动项目

(1)、命令窗口输入命令

npm run dev

(2)、打开浏览器,新建标签,输入命令行窗口提示的链接,一般是

http://localhost:8080

在浏览器中我们将会看到这样子的页面:

5.引入vuex并配置

以下使用模块化方式 首先在VScode编辑器打开项目

(1)、在src文件夹下新建文件夹store

(2)、在store文件夹下新建modules文件夹和index.js

  • 在这个index.js里写如下代码:
import Vue from 'vue'
import Vuex from 'vuex'

import modules from './modules'

Vue.use(Vuex)

export default new Vuex.Store({
  modules
})

(3)、在modules文件夹下新建文件index.jscar.js

car.js:购物车模块store;car.js就是我们最终需要写的vuex流程

  • modules文件夹下index.js里写如下代码,目的为了不需要一个个引入模块里的store:
/**
 * The file enables `@/store/index.js` to import all vuex modules
 * in a one-shot manner. There should not be any reason to edit this file.
 */

const files = require.context('.', false, /\.js$/)
const modules = {}

files.keys().forEach(key => {
  if (key === './index.js') return
  modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})

export default modules

  • modules文件夹下car.js里写如下代码
const state = {}

const getters = {}

const mutations = {}

const actions = {}

export default {
    state, 
    getters,
    mutations,
    actions
}

(4)、在入口文件引入store并挂载

在src目录下的main.js里,引入store,挂载到vue实例里,如下图

main.js的代码如下:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

6.简洁页面并添加按钮绑定点击事件

首先看一下src下新增的store文件夹的目录结构

(1)、简洁项目结构

为了直观看到store的state数据状态对象的变化,我们需要简洁结构,删除模板引导页路由和logo图标

  • 进入在src目录下的App.vue

  • App.vue下删除如下代码

(2)、添加按钮,并给按钮添加点击事件

  • 删除后,在符合语法情况下添加一些字段或标签

我是添加button:广西科技大学

  • 在8080端口下看到的是这样子的显示页面

  • 给button绑定点击事件show,并添加相应的method方法对象

我在show方法里打印vuex的store

  • 查看控制台打印的store值

在浏览器8080端口,点击按钮,查看控制台打印的store的state对象

我们发现,现在可以查看得到store值了,而且state下有我们的car模块, 证明我们对store的引入和store模块modules操作,完全正确

7.开始编写vuex流程

  • 假设: 购物车car里有一个共享的数据状态对象为ProID商品id

  • 现实现一个功能: 每点击一次按钮时,商品id增加1 , 根据流程,我们写一下实现逻辑:

    • 1:点击按钮,触发按钮绑定的方法
    • 2:在按钮绑定的方法里dispatch发送修改请求给actions中的相应方法
    • 3:在actions的相应方法中触发修改动作给mutations中相应修改方法
    • 4:在mutations中相应修改方法直接修改state里的商品id,实现商品id增加

根据实现逻辑,我们执行以下步骤:

(1)、打开store下的modules文件夹下的car.js,

如果不知道actions、dispatch等的参数是什么,建议你先看看上文我总结出来的参数表格

点击查看actions、mutations实例参数

点击查看dispatch、commit实例

  • 在state里,添加共享商品状态,商品id
proID:1, //商品id

  • 在actions里,添加触发动作方法
touchchangeID({commit}){
    commit("changeID"); //提交动作
}

  • 在mutations里,添加直接修改状态方法
changeID(state){
    state.proID ++; //直接修改state状态对象 
}

  • 看看最终的car.js文件是什么样的
const state = {
    proID:1, //商品id
}

const getters = {}

const mutations = {
    changeID(state){
        state.proID ++; //直接修改state状态对象 
    }
}

const actions = {
    touchchangeID({commit}){
        commit("changeID"); //提交动作
    }
}

export default {
    state, 
    getters,
    mutations,
    actions
}

(2)、进入App.vue模板文件,修改按钮绑定方法,执行以下步骤

  • 把按钮显示文字改为从store获取到的共享状态对象商品id
{{this.$store.state.car.proID}}

  • 在按钮绑定方法里修改,并添加触发修改请求代码
this.$store.dispatch("touchchangeID");
console.log(this.$store.state.car.proID)

  • 我们来看看App.vue修改后是怎么样的
<template>
  <div id="app" >
    <button @click="show">

      {{ this.$store.state.car.proID }}

    </button>
  </div>
</template>

<script>
import { mapState,mapGetters,mapMutations,mapActions } from "vuex";

export default {
  name: 'App',
  methods:{

    show(){
      this.$store.dispatch("touchchangeID");
      console.log(this.$store.state.car.proID)

    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

(3)、在浏览器8080端口,测试结果

  • 在项目运行的端口里,我们可以看到,页面是这样的:

  • 点击按钮

state改变了,实现了我们想要的效果

如果你想确认store是否修改了,可以修改一下APP.vue里的修改一下show方法直接在控制台调试出来

回到控制台再点击按钮看输出信息并展开,查看proID值

恭喜你!!,完成了一个vuex流程的小实例

state

state 状态对象、单一状态树

const state = {...} 或 const state (){}

如何取值: 由于我们已经在入口文件Vue实例上挂载了store了

所以页面中,我们可以直接在vue模板中使用$store这个全局对象

state 显示数据

在组件标签中显示state(无命名空间的state 获取):

(1)直接this.$store.state

(2)使用mapState扩展参数到computed计算属性中,再在页面使用该参数

在组件script标签中获取state:

和在组件标签中显示state是一样的操作,我们可以直接使用this.$store.state来获取,或者使用mapState扩展参数到computed计算属性中,再在引用该参数

在外部js中获取state:

首先我们在项目src目录下新建一个js文件夹,在js文件夹下新建一个js叫settleCar.js,写下如下代码

import store from '../store' //引入store

export default{

     countCar : ()=>{
        console.log("外部js的store : " + store.state.car.proID)
    }
}

这样就可以了,如果想测试是否真的获取到了,我们可以再APP.vue组件中引入一下settleCar.js,调用countCar看看控制台输出的值

当我们点击按钮,控制台就会输出如下:

证明我们在settleCar.js中可以正常调用我们的store

在各命名空间里相互调用store

比如命名空间下的A模块想要调用B模块的state

1. 可以通过actions里的各个方法的参数context

B模块state = context.rootState + B模块.state名

context上下文里的参数:


const actions = {
    touchUser(context,payload){
        let Bstate = context.rootState.Bstate
        console.log(Bstate)
    }
}

2. 可以通过getters里的各个方法的参数

B模块state = rootState + B模块.state名

const getters = {
    getUser(rootState){
        let Bstate = rootState.Bstate
        console.log(Bstate)
    }
}

state扩展函数 mapState

在绑定了命名空间后,在组件中,引入import { mapState } from "vuex"后,就可以在计算属性中,使用开展函数都可以这样子写:

 computed: {
    ...mapState("car", {
      proID: state => state.proID, 
    })
 }

当计算属性名与state中的数据名一样(相同)时

可以简写成:

 computed: {
    ...mapState("car",["proID"])
 }

特别要注意,state里其实是可以写函数的

例如:我在modules里有一个car对象(声明了命名空间),我们打印store来看一下:

而且我们可以用,

getters

getters 包含多个getter计算属性函数的对象

const getters = {...}

getters 是对state的进一步封装,比如,当在很多地方获取商品ID时,我希望每次都是能获取到的是,该商品的名称+id,以明白是哪个产品的id.这个时候,我们就需要getters来对我们的id和商品名称进行组合:

我们修改一下car.js 添加getters

const state = {
    proID:1, //商品id
    proName:"数媒",//商品名
}

const getters = {
    proNameID(state){
        let proNameID = state.proName + state.proID;
        return proNameID
    }
}

const mutations = {
    changeID(state){
        state.proID ++; //直接修改state状态对象 
    }
}

const actions = {
    touchchangeID({commit}){
        commit("changeID"); //提交动作
    }
}

export default {
    state, 
    getters,
    mutations,
    actions
}

然后在App.vue中应用

然后我们可以点击按钮后看控制台输出

证明我们getters使用正确

这里值得注意的是:即使getters是定义在car模块里的,但是当这个模块没有声明命名空间时,Vuex会把它作为vuex的store里外部的getters,actions和mutations也一样,都是在外部的,看官方的描述:

模块内部的 action、mutation 和 getter是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名

getters 获取

1. 无命名空间的getters 获取

1.1 组件标签中(无命名空间的getters 获取)

无命名空间的getters在组件标签中获取:

  • (1) 直接this.$store.getters + getters方法名

  • (2)或者我们使用mapGetters映射到computed后再使用computed映射好的属性

{{this.proNameID}}也可以这样子写{{proNameID}}

1.2 组件script中(无命名空间的getters 获取)

以下两种方式都可以,推荐使用第二种


...mapGetters(['proNameID']) ,//1

...mapGetters({   //2
  proNameID:'proNameID'
})

1.3 外部js中(无命名空间的getters 获取)

(1) 首先在外部组件中先引入store, import store from '../store' //引入store

(2)然后在方法里直接通过 store.getters.getters方法

首先我们修改一下settleCar.js


import store from '../store' //引入store

export default{

     countCar : ()=>{
        console.log("外部js的getters : " + store.getters.proNameID)
    }
}

然后在App.vue组件中,引入外部js后,在按钮绑定方法里调用外部js的方法

App.vue组件完整代码在图下方

App.vue组件完整代码

<template>
  <div id="app" >
    <button @click="show">
      
      <!-- {{ proNameID }} -->    <!-- 也可以这样写 -->
       {{ this.proNameID }}

    </button>
  </div>
</template>

<script>
import { mapState,mapGetters,mapMutations,mapActions } from "vuex";
import settleCar from './js/settleCar'

export default {
  name: 'App',
  computed:{
    // ...mapGetters(['proNameID']),//组件script中(无命名空间的getters 获取)

    ...mapGetters({              //组件script中(无命名空间的getters 获取)
      proNameID:'proNameID'
    })
  },
  methods:{
    show(){
      settleCar.countCar()
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

在浏览器项目运行窗口,当我们点击按钮后,观察控制台输出

证明我们在外部引用的js可以正确获取到getters

2. 命名空间下的getters 获取

那么当我们使用了命名空间之后,如何取值呢

首先我们在模块car.js里设置命名空间namespaced: true

2.1 组件标签中(命名空间下的getters 获取)

请确保模块car.js里设置命名空间namespaced: true,查看上文的设置

命名空间下的getters在组件标签中获取:

  • (1)直接this.$store.getters ['命名空间/getters方法名']

  • (2)或者我们使用mapGetters映射到computed后再使用computed映射好的属性

以下两种扩展都可以:

2.2 组件script中(命名空间下的getters 获取)

请确保模块car.js里设置命名空间namespaced: true,查看上文的设置

以下两种方式都可以,推荐使用第二种


...mapGetters('car',['proNameID']) ,//1

...mapGetters({   //2
 proNameID:'car/proNameID'
})

2.3 外部js中(命名空间下的getters 获取)

请确保模块car.js里设置命名空间namespaced: true,查看上文的设置

(1) 首先在外部组件中先引入store , import store from '../store' //引入store

(2)然后在方法里直接通过 store.getters['命名空间/该命名空间下的getters方法']

首先修改一下settleCar.js

import store from '../store' //引入store

export default{

     countCar : ()=>{
        console.log("外部js的getters : " + store.getters['car/proNameID'])
    }
}

在App.vue组件中,引入外部js后,在按钮绑定方法里调用外部js的方法

App.vue组件完整代码在图下方

App.vue组件完整代码


<template>
  <div id="app" >
    <button @click="show">
      
      <!-- {{ proNameID }} -->    <!-- 也可以这样写 -->
       {{ this.proNameID }}

    </button>
  </div>
</template>

<script>
import { mapState,mapGetters,mapMutations,mapActions } from "vuex";
import settleCar from './js/settleCar'

export default {
  name: 'App',
  computed:{
    // ...mapGetters('car',['proNameID']),//组件script中(命名空间下的getters 获取)

    ...mapGetters({              //组件script中(命名空间下的getters 获取)
      proNameID:'car/proNameID'
    })
  },
  methods:{
    show(){
      settleCar.countCar()
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

在浏览器项目运行端口,点击按钮后查看控制台输出信息

证明我们正在外部js里正确地获取到getters了

getters传递参数

getters默认是不能传递参数的,如果希望传递参数,那么只能让getters本身返回另一个函数,比如我们希望根据id来对数据进一步操作,


const getters = {
    nameID(state, getters){
       // return function(id){
       //     return state.proName + id
       // }
       
       return id => state.proName + id
    }
}

然后使用的时候就这样的, 例如标签中 <div> {{ $store.getters.car.nameID(8) }} </div>

而这个 参数 8 就是 传递过去的id

---------以下待编写-----------

actions

actions 包含多个对应事件回调函数的对象

const actions = {...}

mutations

mutations 包含多个更新state函数的对象

const mutations = {...}

vuex 在electron项目里的注意事项

vuex-electron官网 查看vuex-electron官网

在electron项目中,使用vuex,你可能会发现明明书写正确,但是却无法修改state或调用actions、getters、mutations无效。

影响因素是: 首先最主要影响vuex的store的是,在实例化store时,引用插件不正确。 比如vuex-electron里的插件

vuex-electron介绍

跳过介绍,直接查看vuex在electron项目里无效的解决方案

你可以先去官网 查看vuex-electron官网 介绍

  • vuex-electron:在所有进程(包括主进程)之间共享Vuex存储的最简单方法
  • 特性:
    • 数据持久化 Persisted state
    • 共享数据修改 Shared mutations
  • 版本支持

vuex-electron使用

1、 安装vuex-electron

- ```yarn install vuex-electron 或 npm install vuex-electron```

2、 引入到vuex的store里

src\renderer\store\index.js

3、 主进程引入store

如果启用createsharedvariables()插件,则需要在主进程中创建store实例。只需将这一行添加到主进程中

(如果不在主进程中引入store,会导致vuex的store修改无效)

import '../renderer/store'

4、注意

electron项目中如果你使用了vuex-electron的插件,请在 render渲染进程中调用dispatch或mapActions。 不要使用commit,因为通过commit触发的操作不会在进程之间共享, 所以electron-vue项目中,尽量不要在组件里或其他渲染进程文件里直接commit提交修改请求到mutations。

5、 createPersistedState()的可用选项

createPersistedState({
  whitelist: ["whitelistedMutation", "anotherWhitelistedMutation"],

  // 或者

  whitelist: (mutation) => {
    return true
  },

  // 或者

  blacklist: ["ignoredMutation", "anotherIgnoredMutation"],

  // 或者

  blacklist: (mutation) => {
    return true
  }
})

vuex在electron项目里无效的解决方案

在这之前,你需要了解这个vuex-electron插件是做什么用的,然后根据你的项目需求来选择对应的方案 或者看上文的vuex-electron介绍

1、vuex在electron项目里无效的解决方案1(不推荐)

  • 方案:注释createSharedMutations()插件
  • 前提:不需要进程间(多窗口)共享VUEX的Mutations()
  • 影响:去掉createSharedMutations()插件后应该会影响多窗口的传值

进入store文件夹目录下的index.js,注释createSharedMutations(), 路径:src\renderer\store\index.js

这样就可以正常了

2、vuex在electron项目里无效的解决方案2(推荐)

  • 方案:在主进程中引入store
  • 前提:需要进程间(多窗口)共享VUEX的数据和数据修改,即:
  • 需要数据持久化 Persisted state
  • 需要共享数据修改 Shared mutations
  • 影响:不能直接在渲染进程中里commit提交修改请求到mutations,只能先调用dispatch或mapActions后再由action里commit提交修改动作给mutations。

进入主进程src\main\index.js,添加store(根据你的store存放路径来引入)

一般的路径是../renderer/store

import '../renderer/store'

electron项目里尽量不要在渲染进程中直接commit提交

electron项目中如果你使用了vuex-electron的插件,请在 render渲染进程中调用dispatch或mapActions。 不要使用commit,因为通过commit触发的操作不会在进程之间共享, 所以electron-vue项目中,尽量不要在组件里或其他渲染进程文件里直接commit提交修改请求到mutations。



Vue项目中5个经典Vuex插件

这一部分内容转载自:Vue.JS项目中5个经典Vuex插件

1. 状态持久化___vuex-persistedstate

vuex-persistedstate 使用浏览器的本地存储( local storage )对状态( state )进行持久化。这意味着刷新页面或关闭标签页都不会删除你的数据。

一个很好的例子就是购物车:如果用户不小心关闭了一个标签,他们可以重新打开并回到之前页面的状态。

2. 同步标签页、窗口___vuex-shared-mutations

vuex-shared-mutations可在不同的标签页之间同步状态。它通过 mutation 将状态储存到本地存储(local storage)来实现。选项卡、窗口中的内容更新时触发储存事件,重新调用 mutation ,从而保持状态同步。

3. 语言本地化___vuex-i18n

vuex-i18n 允许你轻松地用多种语言存储内容。让你的应用切换语言时更容易。

一个很酷的功能是你可以存储带有标记的字符串,比如"Hello {name}, this is your Vue.js app."。所有的翻译版本都会在标记的地方使用相同的字符串。

4. 管理多个加载状态__vuex-loading

vuex-loading 有助于你管理应用中的多个加载状态。这个插件适用于状态变化频繁且复杂的实时应用程序。

5. 缓存操作__vuex-cache

vuex-cache 可以缓存 Vuex 的 action。例如,如果你从服务器检索数据,这个插件将在第一次调用该 action 时缓存结果,然后在之后的dispatch中,直接返回缓存的值。必要时清除缓存也很简单。

参考