前端MVC Vue2学习总结(九)——Vuex状态管理插件

1、概要

1.一、Vuex定义与注意事项

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

使用Vue开发中须要将应用拆分红多个组件,可是组件与组件之间数据共享成了一个问题,父子组件实现起来相对简单,有不少兄弟组件和跨多级组件,实现起来过程繁琐,在多人协同开发上,不利于统一管理,Vuex能够解决这些问题。html

1.1.一、状态管理模式

没有使用Vuex时,让咱们看一个简单的 Vue 计数应用:前端

new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template:'<div>{{ count }}</div>',
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})

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

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

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

可是,当咱们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:node

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

对于问题一,传参的方法对于多层嵌套的组件将会很是繁琐,而且对于兄弟组件间的状态传递无能为力。对于问题二,咱们常常会采用父子组件直接引用或者经过事件来变动和同步状态的多份拷贝。以上的这些模式很是脆弱,一般会致使没法维护的代码。git

所以使用Vuex,咱们为何不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,咱们的组件树构成了一个巨大的“视图”,无论在树的哪一个位置,任何组件都能获取状态或者触发行为!es6

另外,经过定义和隔离状态管理中的各类概念并强制遵照必定的规则,咱们的代码将会变得更结构化且易维护。github

这就是 Vuex 背后的基本思想,借鉴了  FluxRedux、和  The Elm Architecture。与其余模式不一样的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。spring

 

1.1.二、使用 Vuex

虽然 Vuex 能够帮助咱们管理共享状态,但也附带了更多的概念和框架。这须要对短时间和长期效益进行权衡。

若是您不打算开发大型单页应用,使用 Vuex 多是繁琐冗余的。确实是如此——若是您的应用够简单,您最好不要使用 Vuex。一个简单的  store 模式就足够您所需了。可是,若是您须要构建一个中大型单页应用,您极可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为天然而然的选择。引用 Redux 的做者 Dan Abramov 的话说就是:"Flux 架构就像眼镜:您自会知道何时须要它。"

1.1.三、注意事项

Vuex会有必定的门槛和复杂性,它的主要使用场景是大型单页面应用,若是你的项目不是很复杂,用一个bus也能够实现数据的共享(在前面讲组件的内容中已经讲到过bus做为总线进行通讯的示例),可是它在数据管理,维护,还只是一个简单的组件,而Vuex能够更优雅高效地完成状态管理,因此,是否使用Vuex取决于你的团队和技术储备。

使用bus做为总线通讯的示例:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Vue2 Demo</title>
</head>
<body>
<div id="app01">
    <my-comp1></my-comp1>
    <my-comp2></my-comp2>
</div>
<script src="../../js/vue/vue.js"></script>
<script>
    //事件总线
    var bus = new Vue();

    Vue.component("my-comp1", {
        template: "<button @click='incrN'>{{n}}</button>",
        data() {
            return {n: 0}
        },
        methods: {
            incrN: function () {
                this.n++;
                //发布事件
                bus.$emit("inc",this.n);
            }
        }
    });


    Vue.component("my-comp2", {
        template: "<button @click='incrN'>{{n}}</button>",
        data() {
            return {n: 999}
        },
        methods: {
            incrN: function () {
                this.n--;
            }
        },
        //勾子,挂载完成时执行事件
        mounted:function () {
            var _this=this;
            //监听事件,订阅事件
            bus.$on("inc",function (val) {
                _this.n+=val;
            })
        }
    });

    var vm = new Vue({
        el: "#app01",
        data: {}
    });
</script>
</body>
</html>
View Code

1.二、概念

每个 Vuex 应用的核心就是store(仓库),store基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。 Vuex和单纯的全局对象有如下两点不一样:

1.Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地获得高效更新;

2.你不能直接改变 store 中的状态。改变 store 中的状态的惟一途径就是显式地提交 (commit) mutation。这样使得咱们能够方便地跟踪每个状态的变化,从而让咱们可以实现一些工具帮助咱们更好地了解咱们的应用。

  • store:表示对Vuex对象的全局引用。组件经过Store来访问Vuex对象中的State。
  • state:保存数据的状态、对象的状态,即其所拥有的数据。
  • getter:至关于Store的计算属性。由于就像计算属性同样,Getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被从新计算。下面会说到具体的使用场景。
  • mutations:定义了对state中数据的修改操做,更改store中的状态的惟一方法是提交mutation。mutation相似于事件:每一个mutation都有一个字符串的事 件类型(type),和一个回调函数(handler).利用store.commit('方法名')来调用这个函数。
  • mutations-type:能够认为是store中的计算属性,mapGetters是辅助函数,仅仅将store中的getter映射到局部计算属性。
  • action :mutation中定义的操做只能执行同步操做,Vuex中的异步操做在Action中进行,Action最终经过调用Mutation的操做来更新数据;相似于mutation,不一样在于action提交的是mutation,而不是直接变动状态,action能够包含任意 异步操做。action用store.dispatch方法触发函数。mapActions是辅助函数,将组件的 methods 映射为store.dispatch。
  • module:Store和State之间的一层,便于大型项目管理,Store包含多个Module,Module包含State、Mutation和Action。

1.三、资源

github: https://github.com/vuejs/vuex

中文帮助: https://vuex.vuejs.org/zh/(本文大量引用)

英文帮助: https://vuex.vuejs.org/

视频教程: https://www.bilibili.com/video/av17503637/

2、安装

2.一、直接下载或CDN 引用

引用地址: https://unpkg.com/vuex

Unpkg.com 提供了基于 NPM 的 CDN 连接。以上的连接会一直指向 NPM 上发布的最新版本。您也能够经过 https://unpkg.com/vuex@3.0.1/dist/vuex.js这样的方式指定特定的版本。

CDN引用:

<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

也可使用下载后本地引用,vuex的github地址是: https://github.com/vuejs/vuex

2.二、NPM包管理器

npm i vuex --save

2.三、Yarn

yarn add vuex

在一个模块化的打包系统中,您必须显式地经过 Vue.use() 来安装 Vuex:

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

Vue.use(Vuex)

//当使用全局 script 标签引用 Vuex 时,不须要以上安装过程。

2.四、Promise

Vuex 依赖 Promise。若是你支持的浏览器并无实现 Promise (好比 IE),那么你可使用一个polyfill的库,例如 es6-promise。

你能够经过 CDN 将其引入:

<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>

而后 window.Promise 会自动可用。

若是你喜欢使用诸如 npm 或 Yarn 等包管理器,能够按照下列方式执行安装:

npm install es6-promise --save # npm
yarn add es6-promise # Yarn

或者更进一步,将下列代码添加到你使用 Vuex 以前的一个地方:

import 'es6-promise/auto'

2.五、本身构建

若是须要使用 dev 分支下的最新版本,您能够直接从 GitHub 上克隆代码并本身构建。

git clone https://github.com/vuejs/vuex.git node_modules/vuex
cd node_modules/vuex
npm install
npm run build

3、应用

3.一、页面使用Vuex快速起步

建立一个 store,建立过程直截了当——仅须要提供一个初始 state 对象和一些 mutation:

// 若是在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

如今,你能够经过 store.state 来获取状态对象,以及经过 store.commit 方法触发状态变动:

store.commit('increment')

console.log(store.state.count) // -> 1

再次强调,咱们经过提交 mutation 的方式,而非直接改变 store.state.count,是由于咱们想要更明确地追踪到状态的变化。这个简单的约定可以让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让咱们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,咱们甚至能够实现如时间穿梭般的调试体验。

因为 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅须要在计算属性中返回便可。触发变化也仅仅是在组件的 methods 中提交 mutation。

完整示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p>{{ count }}</p>
    <p>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
    </p>
    <div>
        <comp1></comp1>
        <comp1></comp1>
    </div>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script>

    //定义仓库对象
    const store = new Vuex.Store({
        state: {
            count: 0
        },
        mutations: {
            increment: state => state.count++,
            decrement: state => state.count--
        }
    })

    //定义组件
    Vue.component("comp1",{
        template:"<h2>{{count}}</h2>",
        computed: {
            count () {
                return store.state.count
            }
        }
    });

    new Vue({
        el: '#app',
        computed: {
            count () {
                return store.state.count
            }
        },
        methods: {
            increment () {
                store.commit('increment')
            },
            decrement () {
                store.commit('decrement')
            }
        }
    })
</script>
</body>
</html>

运行结果:

 

在线示例:这是一个 最基本的 Vuex 记数应用示例

3.二、Vue-cli中使用Vuex快速起步

安装vuex:

插件引用:

//导入插件
import Vuex from 'vuex'

//使用插件
Vue.use( Vuex );

//定义仓库对象
const store = new Vuex.Store({
    //属性
})

//定义vue实例并关联存储仓库
new Vue({
    el: '#app',
    store,
    render: h => h(App)
});

定义状态对象main.js:

import Vue from 'vue'
import Vuex from 'vuex';
import App from './App'
import router from './router/hello'

Vue.config.productionTip = false

Vue.use(Vuex);

//定义仓库对象
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: state => state.count++,
    decrement: state => state.count--
  }
})

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

直接使用状态对象中的数据App.vue:

<template>
  <div>
    <img src="./assets/logo.png">
    <h2>
      {{$store.state.count}}
    </h2>
    <Counter/>
    <header>
      <!-- router-link 定义点击后导航到哪一个路径下 -->
      <router-link to="/" exact>index</router-link>
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar">Go to Bar</router-link>
    </header>
    <!-- 对应的组件内容渲染到router-view中 -->
    <router-view></router-view>
  </div>
</template>
<script>
  import Counter from './components/Counter';

  export default {
    components:{Counter}
  }
</script>
<style scoped>
  a {
    color: #777;
  }

  a:hover {
    color: orangered;
  }
</style>
View Code

在组件中使用状态对象:

//Counter.vue
<template>
  <div id="app">
    <h2>{{ count }}</h2>
    <p>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </p>
  </div>
</template>

<script>
  export default {
    name: "Counter",
    computed: {
      count() {
        return this.$store.state.count
      }
    },
    methods: {
      increment() {
        this.$store.commit('increment')
      },
      decrement() {
        this.$store.commit('decrement')
      }
    }
  }
</script>

<style scoped>
  h2 {
    color: darkblue;
  }
</style>

引用组件:

//bar.vue

<template>
  <div>
    <h2>Bar</h2>
    <p>{{msg}}</p>
    <Counter/>
  </div>
</template>
<script>
  import Counter from './Counter';

  export default {
    data() {
      return {
        msg: "我是Bar组件"
      }
    },
    components: {Counter}
  }
</script>
<style scoped>
  h2 {
    color: springgreen;
  }
</style>

App.Vue内容:

<template>
  <div>
    <img src="./assets/logo.png">
    <h2>
      {{$store.state.count}}
    </h2>
    <Counter/>
    <header>
      <!-- router-link 定义点击后导航到哪一个路径下 -->
      <router-link to="/" exact>index</router-link>
      <router-link to="/foo">Go to Foo</router-link>
      <router-link to="/bar">Go to Bar</router-link>
    </header>
    <!-- 对应的组件内容渲染到router-view中 -->
    <router-view></router-view>
  </div>
</template>
<script>
  import Counter from './components/Counter';

  export default {
    components:{Counter}
  }
</script>
<style scoped>
  a {
    color: #777;
  }

  a:hover {
    color: orangered;
  }
</style>

运行结果:

切换到一个单页Bar

3.三、没有使用vuex的汽车列表(示例)

为了方便后面的内容讲解这里从新作一个简单的vuex汽车列表示例,这个示例分别有两个组件CarListOne.vue和CarListTwo.vue, 在App.vue的datat中保存着共有的汽车列表, 代码和初始化的效果以下图所示:

 

App.Vue:
<template>
  <div id="app">
    <h2>汽车商城</h2>
    <hr/>
    <car-list-one v-bind:cars="cars"></car-list-one>
    <car-list-two v-bind:cars="cars"></car-list-two>
  </div>
</template>

<script>
  import CarListOne from './components/CarListOne.vue'
  import CarListTwo from './components/CarListTwo.vue'

  export default {
    name: 'app',
    components: {
      'car-list-one': CarListOne,
      'car-list-two': CarListTwo
    },
    data() {
      return {
        cars: [
          {name: '奇瑞', price: 18.3},
          {name: '吉利', price: 19.6},
          {name: '长安', price: 17.5},
          {name: '红旗', price: 21.9}
        ]
      }
    }
  }
</script>

<style>
  h2 {
    color: orangered;
  }
</style>

CarListOne.vue

<template>
  <div id="car-list-one">
    <h2>Car List One</h2>
    <ul>
      <li v-for="car in cars">
        <span>{{ car.name }}</span>
        <span>¥{{ car.price }}万元</span>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    props: ['cars'],
    data() {
      return {}
    }
  }
</script>

<style scoped>
  h2 {
    color: dodgerblue;
  }
</style>

CarListTwo.vue

<template>
  <div id="car-list-two">
    <h2>Car List Two</h2>
    <ul>
      <li v-for="car in cars">
        <button>{{ car.name }}</button>
        <button>¥{{ car.price }}万元</button>
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    props: ['cars'],
    data() {
      return {}
    }
  }
</script>

<style scoped>
  h2 {
    color: limegreen;
  }
</style>

3.四、State

state就是Vuex中的公共的状态, 我是将state看做是全部组件的data, 用于保存全部组件的公共数据。

此时咱们就能够把App.vue中的两个组件共同使用的data抽离出来, 放到state中,代码以下:

//main.js
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'

Vue.use( Vuex )

const store = new Vuex.Store({
  state:{ 
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '长安', price: 60},
      {name: '比亚迪', price: 80}
    ]
  }
})

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
此时,CarListOne.vue和CarListTwo.vue也须要作相应的更改
//CarListOne.vue
export default {
    data () {
        return {
            cars : this.$store.state.cars //获取store中state的数据
        }
    }
}
//CarListTwo.vue
export default {
    data () {
        return {
            cars: this.$store.state.cars //获取store中state的数据
        }
    }
}

此时的页面以下图所示, 能够看到, 将公共数据抽离出来后, 页面没有发生变化。

3.五、Getters

我将getters属性理解为全部组件的computed属性, 也就是计算属性。vuex的官方文档也是说到能够将getter理解为store的计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被从新计算。

此时,咱们能够在main.js中添加一个getters属性, 其中的saleCars对象将state中的价格减小一半(除以2)

//main.js
const store = new Vuex.Store({
  state:{
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '长安', price: 60},
      {name: '比亚迪', price: 80}
    ]
  },
  getters:{ //添加getters
    saleCars: (state) => {
      let saleCars = state.cars.map( car => {
        return {
          name: car.name,
          price: car.price / 2
        }
      })
      return saleCars;
    }
  } 
})
将carListOne.vue中的cars的值更换为this.$store.getters.saleCars
export default {
    data () {
        return {
            cars : this.$store.getters.saleCars 
        }
    }
}

如今的页面中,Car List One中的每项汽车的价格都减小了一半

 

getters 和 vue 中的 computed 相似 , 都是用来计算 state 而后生成新的数据 ( 状态 ) 的。

仍是前面的例子 , 假如咱们须要一个与状态 show 恰好相反的状态 , 使用 vue 中的 computed 能够这样算出来 :

computed(){
    not_show(){
        return !this.$store.state.dialog.show;
    }
}
那么 , 若是不少不少个组件中都须要用到这个与 show 恰好相反的状态 , 那么咱们须要写不少不少个 not_show , 使用 getters 就能够解决这种问题 :

export default {
    state:{//state
        show:false
    },
    getters:{
        not_show(state){//这里的state对应着上面这个state
            return !state.show;
        }
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还能够在这里执行其余的操做改变state
        }
    },
    actions:{
        switch_dialog(context){//这里的context和咱们使用的$store拥有相同的对象和方法
            context.commit('switch_dialog');
            //你还能够在这里触发其余的mutations方法
        },
    }
}
咱们在组件中使用 $store.state.dialog.show 来得到状态 show , 相似的 , 咱们可使用 $store.getters.not_show 来得到状态 not_show 。

注意 : $store.getters.not_show 的值是不能直接修改的 , 须要对应的 state 发生变化才能修改。

 

 

mapState、mapGetters、mapActions
不少时候 , $store.state.dialog.show 、$store.dispatch('switch_dialog') 这种写法又长又臭 , 很不方便 , 咱们没使用 vuex 的时候 , 获取一个状态只须要 this.show , 执行一个方法只须要 this.switch_dialog 就好了 , 使用 vuex 使写法变复杂了 ?

使用 mapState、mapGetters、mapActions 就不会这么复杂了。

以 mapState 为例 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{

    //这里的三点叫作 : 扩展运算符
    ...mapState({
      show:state=>state.dialog.show
    }),
  }
}
</script>
至关于 :

<template>
  <el-dialog :visible.sync="show"></el-dialog>
</template>

<script>
import {mapState} from 'vuex';
export default {
  computed:{
    show(){
        return this.$store.state.dialog.show;
    }
  }
}
</script>
mapGetters、mapActions 和 mapState 相似 , mapGetters 通常也写在 computed 中 , mapActions 通常写在 methods 中。

 

 

3.六、Mutations

我将mutaions理解为store中的methods, mutations对象中保存着更改数据的回调函数,该函数名官方规定叫type, 第一个参数是state, 第二参数是payload, 也就是自定义的参数.

下面,咱们在main.js中添加mutations属性,其中minusPrice这个回调函数用于将汽车的价格减小payload这么多, 代码以下:

//main.js
const store = new Vuex.Store({
  state:{
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '长安', price: 60},
      {name: '比亚迪', price: 80}
    ]
  },
  getters:{
    saleCars: (state) => {
      let saleCars = state.cars.map( car => {
        return {
          name: car.name,
          price: car.price / 2
        }
      })
      return saleCars;
    }
  },
  mutations:{ //添加mutations
    minusPrice (state, payload ) {
      let newPrice = state.cars.forEach( car => {
        car.price -= payload
      })
    }
  }
})
在CarListTwo.vue中添加一个按钮,为其添加一个点击事件, 给点击事件触发minusPrice方法
//CarListTwo.vue
<template>
    <div id="car-list-two">
        <h2>Car List Two</h2>
        <ul>
            <li v-for="car in cars">
                <span class="name">{{ car.name }}</span>
                <span class="price">${{ car.price }}</span>
            </li>
            <button @click="minusPrice">减小价格</button> //添加按钮
        </ul>
    </div>
</template>
在CarListTwo.vue中注册minusPrice方法, 在该方法中commitmutations中的minusPrice这个回调函数
注意:调用mutaions中回调函数, 只能使用store.commit(type, payload)
//CarListTwo.vue
export default {
    data () {
        return {
            cars: this.$store.state.cars
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2); //提交`minusPrice,payload为2
        }
    }
}

添加按钮, 能够发现, Car List Two中的价格减小了2, 固然你能够自定义payload,以此自定义减小对应的价格.

mutations效果


(Car List One中的价格没有发生变化, 是由于getters将价格进行了缓存)

 

前面咱们提到的对话框例子 , 咱们对vuex 的依赖仅仅只有一个 $store.state.dialog.show 一个状态 , 可是若是咱们要进行一个操做 , 须要依赖不少不少个状态 , 那管理起来又麻烦了 !

mutations 登场 , 问题迎刃而解 :

export default {
    state:{//state
        show:false
    },
    mutations:{
        switch_dialog(state){//这里的state对应着上面这个state
            state.show = state.show?false:true;
            //你还能够在这里执行其余的操做改变state
        }
    }
}
使用 mutations 后 , 原先咱们的父组件能够改成 :

<template>
  <div id="app">
    <a href="javascript:;" @click="$store.commit('switch_dialog')">点击</a>
    <t-dialog></t-dialog>
  </div>
</template>

<script>
import dialog from './components/dialog.vue'
export default {
  components:{
    "t-dialog":dialog
  }
}
</script>
使用 $store.commit('switch_dialog') 来触发 mutations 中的 switch_dialog 方法。

这里须要注意的是:

mutations 中的方法是不分组件的 , 假如你在 dialog_stroe.js 文件中的定义了
switch_dialog 方法 , 在其余文件中的一个 switch_dialog 方法 , 那么
$store.commit('switch_dialog') 会执行全部的 switch_dialog 方法。
mutations里的操做必须是同步的。
你必定好奇 , 若是在 mutations 里执行异步操做会发生什么事情 , 实际上并不会发生什么奇怪的事情 , 只是官方推荐 , 不要在 mutationss 里执行异步操做而已。

 

 

3.七、Actions

actions 相似于 mutations,不一样在于:

  • actions提交的是mutations而不是直接变动状态

  • actions中能够包含异步操做, mutations中绝对不容许出现异步

  • actions中的回调函数的第一个参数是context, 是一个与store实例具备相同属性和方法的对象

  • 此时,咱们在store中添加actions属性, 其中minusPriceAsync采用setTimeout来模拟异步操做,延迟2s执行 该方法用于异步改变咱们刚才在mutaions中定义的minusPrice

//main.js
const store = new Vuex.Store({
  state:{
    cars: [
      {name: '奇瑞', price: 20},
      {name: '吉利', price: 40},
      {name: '长安', price: 60},
      {name: '比亚迪', price: 80}
    ]
  },
  getters:{
    saleCars: (state) => {
      let saleCars = state.cars.map( car => {
        return {
          name: car.name,
          price: car.price / 2
        }
      })
      return saleCars;
    }
  },
  mutations:{
    minusPrice (state, payload ) {
      let newPrice = state.cars.forEach( car => {
        car.price -= payload
      })
    }
  },
  actions:{ //添加actions
    minusPriceAsync( context, payload ) {
      setTimeout( () => {
        context.commit( 'minusPrice', payload ); //context提交
      }, 2000)
    }
  }
})
在CarListTwo.vue中添加一个按钮,为其添加一个点击事件, 给点击事件触发minusPriceAsync方法
<template>
    <div id="car-list-two">
        <h2>Car List Two</h2>
        <ul>
            <li v-for="car in cars">
                <span class="name">{{ car.name }}</span>
                <span class="price">${{ car.price }}</span>
            </li>
            <button @click="minusPrice">减小价格</button>
            <button @click="minusPriceAsync">异步减小价格</button> //添加按钮
        </ul>
    </div>
</template>
在CarListTwo.vue中注册minusPriceAsync方法, 在该方法中dispatchactions中的minusPriceAsync这个回调函数
export default {
    data () {
        return {
            cars: this.$store.state.cars
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2);
        },
        minusPriceAsync() {
            this.$store.dispatch('minusPriceAsync', 5); //分发actions中的minusPriceAsync这个异步函数
        }
    }
}

 

  • 添加按钮, 能够发现, Car List Two中的价格延迟2s后减小了5


    actions效果
    actions效果
     
     
     
    多个 state 的操做 , 使用 mutations 会来触发会比较好维护 , 那么须要执行多个 mutations 就须要用 action 了:
    
    export default {
        state:{//state
            show:false
        },
        mutations:{
            switch_dialog(state){//这里的state对应着上面这个state
                state.show = state.show?false:true;
                //你还能够在这里执行其余的操做改变state
            }
        },
        actions:{
            switch_dialog(context){//这里的context和咱们使用的$store拥有相同的对象和方法
                context.commit('switch_dialog');
                //你还能够在这里触发其余的mutations方法
            },
        }
    }
    那么 , 在以前的父组件中 , 咱们须要作修改 , 来触发 action 里的 switch_dialog 方法:
    
    <template>
      <div id="app">
        <a href="javascript:;" @click="$store.dispatch('switch_dialog')">点击</a>
        <t-dialog></t-dialog>
      </div>
    </template>
    
    <script>
    import dialog from './components/dialog.vue'
    export default {
      components:{
        "t-dialog":dialog
      }
    }
    </script>
    使用 $store.dispatch('switch_dialog') 来触发 action 中的 switch_dialog 方法。
    
    官方推荐 , 将异步操做放在 action 中。

     

     

 

3.八、Modules

因为使用单一状态树,应用的全部状态会集中到一个比较大的对象。当应用变得很是复杂时,store 对象就有可能变得至关臃肿。为了解决以上问题,Vuex 容许咱们将 store 分割成模块(module)。每一个模块拥有本身的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行一样方式的分割

 

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

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

 

 

前面为了方便 , 咱们把 store 对象写在了 main.js 里面 , 但实际上为了便于往后的维护 , 咱们分开写更好 , 咱们在 src 目录下 , 新建一个 store 文件夹 , 而后在里面新建一个 index.js :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

export default new vuex.Store({
    state:{
        show:false
    }
})
那么相应的 , 在 main.js 里的代码应该改为 :

//vuex
import store from './store'

new Vue({
  el: '#app',
  router,
  store,//使用store
  template: '<App/>',
  components: { App }
})
这样就把 store 分离出去了 , 那么还有一个问题是 : 这里 $store.state.show 不管哪一个组件均可以使用 , 那组件多了以后 , 状态也多了 , 这么多状态都堆在 store 文件夹下的 index.js 很差维护怎么办 ?

咱们可使用 vuex 的 modules , 把 store 文件夹下的 index.js 改为 :

import Vue from 'vue'
import vuex from 'vuex'
Vue.use(vuex);

import dialog_store from '../components/dialog_store.js';//引入某个store对象

export default new vuex.Store({
    modules: {
        dialog: dialog_store
    }
})
这里咱们引用了一个 dialog_store.js , 在这个 js 文件里咱们就能够单独写 dialog 组件的状态了 :

export default {
    state:{
        show:false
    }
}
作出这样的修改以后 , 咱们将以前咱们使用的 $store.state.show 通通改成 $store.state.dialog.show 便可。

若是还有其余的组件须要使用 vuex , 就新建一个对应的状态文件 , 而后将他们加入 store 文件夹下的 index.js 文件中的 modules 中。

modules: {
    dialog: dialog_store,
    other: other,//其余组件
}

 

 

 

改进的计算器

eg:store.js

import Vue from 'vue';
import Vuex from 'vuex'; //引入 vuex
import store from './store' //注册store

Vue.use(Vuex); //使用 vuex

export default new Vuex.Store({
    state: {
        // 初始化状态
        count: 0,
        someLists:[]
    },
    mutations: {
        // 处理状态
        increment(state, payload) {
            state.count += payload.step || 1;
        }
    },
    actions: {
        // 提交改变后的状态
        increment(context, param) {
            context.state.count += param.step;
            context.commit('increment', context.state.count)//提交改变后的state.count值
        },
        incrementStep({state, commit, rootState}) {
            if (rootState.count < 100) {
                store.dispatch('increment', {//调用increment()方法
                    step: 10
                })
            }
        }
    },
    getters: {
        //处理列表项
        someLists: state =>param=> {
            return state.someLists.filter(() => param.done)
        }
    }
})
使用时,eg:

main.js:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store' //引入状态管理 store

Vue.config.productionTip = false

new Vue({
  router,
  store,//注册store(这能够把 store 的实例注入全部的子组件)
  render: h => h(App)
}).$mount('#app')
views/home.vue:

<template>
  <div class="home">
    <!--在前端HTML页面中使用 count-->
    <HelloWorld :msg="count"/>
    <!--表单处理 双向绑定 count-->
    <input :value="count" @input="incrementStep">
  </div>
</template>

<script>
    import HelloWorld from '@/components/HelloWorld.vue'
    import {mapActions, mapState,mapGetters} from 'vuex' //注册 action 和 state

    export default {
        name: 'home',
        computed: {
            //在这里映射 store.state.count,使用方法和 computed 里的其余属性同样
            ...mapState([
                'count'
            ]),
            count () {
                return store.state.count
            }
        },
        created() {
            this.incrementStep();
        },
        methods: {
            //在这里引入 action 里的方法,使用方法和 methods 里的其余方法同样
            ...mapActions([
                'incrementStep'
            ]),
            // 使用对象展开运算符将 getter 混入 computed 对象中
            ...mapGetters([
                'someLists'
                // ...
            ])
        },
        components: {
            HelloWorld
        }
    }
</script>
相关文章
相关标签/搜索