Vue状态管理之Vuex

一、什么是Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

这个状态自管理应用包含以下几个部分:

  • state,驱动应用的数据源;
  • view,以声明方式将state映射到视图;
  • actions,响应在view上的用户输入导致的状态变化。

以下是一个表示“单向数据流”理念的极简示意:

Vue状态管理之Vuex_第1张图片
单项数据流

更复杂的情况

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。


    Vue状态管理之Vuex_第2张图片
    实际数据流

二、什么情况下我应该使用 Vuex?

虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的

global event bus

就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:

Flux 架构就像眼镜:您自会知道什么时候需要它。

三、安装

首先需要创建一个基于Vue的工程,通常你可以这样做:

# 全局安装 vue-cli
$ npm install --global vue-cli
# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project
# 安装依赖,走你
$ cd my-project
$ npm install
$ npm run dev
  1. 利用npm包管理工具,进行安装 vuex。在控制命令行中输入下边的命令就可以了。
npm install vuex --save

要注意的是这里一定要加上 –save,因为你这个包我们在生产环境中是要使用的。

通过简单的依赖安装操作,vuex就算引用成功了,接下来我们就可以尽情的玩耍了。
一个简单的示例,在main.js文件中输入如下代码:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
store.commit('increment')

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: `
{{ count }}
`, computed: { count () { return store.state.count } } })

运行后可以看到页面上显示一个1,即函数increment已经正确的执行,count由初始的0自加后变为1输出,
需要指出的是,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。

通常我们不会这样去使用它,更好的使用方法是:

  1. 新建一个store文件夹(这个不是必须的),并在文件夹下新建store.js文件,文件中引入我们的vue和vuex。
import Vue from 'vue';
import Vuex from 'vuex';
  1. 在store.js文件中,用Vue.use对vuex进行引用。
Vue.use(Vuex);

4.在main.js 中引入新建的vuex文件

import store from './store/store'
  1. Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex),通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。:
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/store'

Vue.config.productionTip = false

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

四、使用

  1. 现在我们store.js文件里增加一个常量对象。
state: {
  count: 1
},
  1. 用export default 封装代码,让外部可以引用。
export default new Vuex.Store({
  name: 'store',
  state: {
    count: 1
  }
})
  1. 新建一个Vue的测试模板,位置在components文件夹下,名字随便起,我这里叫count.vue。在模板中我们使用{{$store.state.count}}输出count 的值(注意:由于前面已经将store注入到vue的实例中,故不需要再次引用)。
 

  1. 在store.js文件中加入两个改变state的方法。
mutations: {
    increment (state) {
      state.count += 1
    },
    decrement (state) {
      state.count -= 1
    }
  }
  1. 在test.vue模板中加入两个按钮,并调用mutations中的方法。
  1. 在router文件夹下的index.js中引用文件,定义对应的路由,运行程序并进入该界面,点击按钮查看效果
{
   path: '/count',
   name: 'Count',
   component: Count
}

五、state访问状态对象

state,这个就是我们说的访问状态对象,它就是我们SPA(单页应用程序)中的共享值。

访问状态对象赋值给内部对象,也就是把stroe.js中的值,赋值给我们模板里data中的值。有三种赋值方式

  1. 通过computed的计算属性直接赋值
    computed属性可以在输出前,对data中的值进行改变,我们就利用这种特性把store.js中的state值赋值给我们模板中的data值。
    computed:{
        count(){
            return this.$store.state.count;
        }
    }

这里需要注意的是return this.$store.state.count这一句,一定要写this,要不你会找不到$store的。这种写法很好理解,但是写起来是比较麻烦的,那我们来看看第二种写法。

  1. 通过mapState的对象来赋值

我们首先要用import引入mapState。

    import {mapState} from 'vuex'

然后还在computed计算属性里写如下代码:(根据需要选用一种方法即可)

   computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,   // es6写法,function (state) { return state.count }

    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
  1. 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])
  1. 还可以采用简化写法(个人比较喜欢这种写法本质上和第三种是一个意思,后面的mutations中也有类似的写法)
// 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
      num: 'count'
    })

这个算是最简单的写法了,在实际项目开发当中也经常这样使用。

六、Mutations

我们已经定义并且拿到了state的值,那么如何去改变他们的状态呢?
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数

  1. 修改状态
    Vuex提供了commit方法来修改状态,前边我们在给按钮添加点击事件时已经使用过该方法了,代码如下。
   
   

store.js文件:

mutations: {
    increment (state) {
      state.count += 1
    },
    decrement (state) {
      state.count -= 1
    }
}

传值:这只是一个最简单的修改状态的操作,在实际项目中我们常常需要在修改状态时传值。比如上边的例子,是我们每次只加1,而现在我们要通过所传的值进行相加。其实我们只需要在Mutations里再加上一个参数,这个参数又称为mutation 的 载荷(payload),并在commit的时候传递就就可以了。我们来看具体代码:

现在store.js文件里给increment 方法加上一个参数n。

    mutations: {
    increment (state, n) {
      state.count += n
    },
    decrement (state) {
      state.count -= 1
    }
  }

在count.vue里修改按钮的commit( )方法传递的参数,我们传递10,意思就是每次加10。

    

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
  amount: 10
})

更推荐的写法:对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

store.commit({
  type: 'increment',
  amount: 10
})
  1. 上面的写法还是复杂了点,有没有更简单的呢,答案是肯定的,模板获取Mutations方法

实际开发中我们也不喜欢看到$store.commit( )这样的方法出现,我们希望跟调用模板里的方法一样调用。

例如:@click=”reduce” 就和没引用vuex插件一样。要达到这种写法,只需要简单的两部就可以了:

1)、在模板count.vue里用import 引入我们的mapMutations:

    import { mapState,mapMutations } from 'vuex';

2)、在模板的<\script>标签里添加methods属性,并加入mapMutations

     methods:mapMutations([
            'increment '
    ]),

通过上边两部,我们已经可以在模板中直接使用我们的reduce或者add方法了,就像下面这样。

    

同state一样,该写法也可以利用对象展开运算符进行简化

...mapMutations({
    add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})

调用

    

七、getters计算过滤操作

getters从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。你可以把它看作store.js的计算属性。

  1. getters基本用法:
    比如我们现在要对store.js文件中的count进行一个计算属性的操作,就是在它输出前,给它加上100.我们首先要在store.js里用const声明我们的getters属性。
 getters: {
    count: state => {
      return (state.count += 100)
    }

在store.js里的配置算是完成了,我们需要到模板页对computed进行配置。在vue 的构造器里边只能有一个computed属性,如果你写多个,只有最后一个computed属性可用,所以要对上节课写的computed属性进行一个改造。

    ...mapState({
      name: 'name',
      count () {
        return this.$store.getters.count
      }
    })

使用

{{count}}

需要注意的是,你写了这个配置后,在每次count 的值发生变化的时候,都会进行加100的操作。

  1. 用mapGetters简化模板写法

我们都知道state和mutations都有map的引用方法把我们模板中的编码进行简化,我们的getters也是有的,我们来看一下代码。
首先用import引入我们的mapGetters

import { mapState, mapMutations, mapGetters } from 'vuex'

在computed属性中加入mapGetters

 ...mapGetters({
      counted: 'count'
    })

八、actions异步修改状态

actions和之前讲的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态。

  1. 在store.js中声明actions
    actions是可以调用Mutations里的方法的,我们还是继续在上节课的代码基础上进行学习,在actions里调用increment和decrement两个方法。
actions: {
    incrementAsync ({ commit }, payload) {
      setTimeout(() => {
        commit('increment', payload)
      }, 1000)
    },
    decrementAction (context) {
      context.commit('decrement')
    }
  }

在actions里写了两个方法incrementAction 和decrementAction ,在方法体里,我们都用commit调用了Mutations里边的方法。细心的小伙伴会发现这两个方法传递的参数也不一样。

  • ontext:上下文对象,这里你可以理解成store本身。
  • {commit}:直接把commit对象传递过来,可以让方法体逻辑和代码更清晰明了。
  1. 模板中的使用

我们需要在count.vue模板中编写代码,让actions生效。我们先复制两个以前有的按钮,并改成我们的actions里的方法名,分别是:incrementAction 和decrementAction 。

     
     

改造一下我们的methods方法,首先还是用扩展运算符把mapMutations和mapActions加入。

    methods:{
       ...mapActions({
           incrementAsync: 'incrementAsync', // 将 `this.add()` 映射为 `this.$store.commit('increment')`
          decrementAction: 'decrementAction'
       })
    },

你还要记得用import把我们的mapActions引入才可以使用。

九、module模块组

随着项目的复杂性增加,我们共享的状态越来越多,这时候我们就需要把我们状态的各种操作进行一个分组,分组后再进行按组编写。那今天我们就学习一下module:状态管理器的模块组操作。

  1. 声明模块组:

在vuex/store.js中声明模块组,我们还是用我们的const常量的方法声明模块组。代码如下:

    const moduleA={
        state,mutations,getters,actions
    }

声明好后,我们需要修改原来 Vuex.Stroe里的值:

    export default new Vuex.Store({
        modules:{a:moduleA}
    })
  1. 在模板中使用

现在我们要在模板中使用count状态,要用插值的形式写入。

    

{{$store.state.a.count}}

如果想用简单的方法引入,还是要在我们的计算属性中rutrun我们的状态。写法如下:

    computed:{
        count(){
            return this.$store.state.a.count;
        }
    },

你可能感兴趣的:(Vue状态管理之Vuex)