2-快速攻破vuex五大核心-附案例源码

文章目录

1. 核心概念

  • state
  • getters
  • mutations
  • actions

2. state

2.1 单一状态树

Vuex使用单一状态树——用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。

这也意味着,每个应用将仅仅包含一个 store实例。

单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

单状态树和模块化并不冲突——后面我们会讨论如何将状态和状态变更事件分布到各个子模块中。

存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,例如状态对象必须是纯粹 (plain) 的。参考:Vue#data

2.2 在 Vue 组件中获得 Vuex 状态

那么我们如何在 Vue 组件中展示状态呢?

由于 Vuex 的状态存储是响应式的,从store 实例中读取状态最简单的方法就是在计算属性中返回某个状态(上一篇的案例中已演示):

// 创建一个 Counter 组件
const Counter = {
    
    
  template: `<div>{
     
     { count }}</div>`,
  computed: {
    
    
    count () {
    
    
      return store.state.count
    }
  }
}

每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM

然而,这种模式导致组件依赖全局状态单例。

在模块化的构建系统中,在每个需要使用state 的组件中需要频繁地导入,并且在测试组件时需要模拟状态。

Vuex通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):

const app = new Vue({
    
    
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  components: {
    
     Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

通过在根实例中注册 store 选项,该store实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。让我们更新下 Counter 的实现:

const Counter = {
    
    
  template: `<div>{
     
     { count }}</div>`,
  computed: {
    
    
    count () {
    
    
      return this.$store.state.count
    }
  }
}

2.3 state 的创建

存储应用状态数据的对象,state 的值可以是一个对象,也可以是一个返回对象的函数,类似 vue 中组件的 data ,使用函数的方式返回对象可以避免对象引用导致的副作用问题(与Vue的data属性必须返回一个函数的原因一致,请参考

// let state = {
    
    
//   a: 1
// }
let state = _=>({
    
    a:1})

const store = new Vuex.Store({
    
    
  	state
})
const store2 = new Vuex.Store({
    
    
    state
})

console.log(store.state == store2.state)
store.state.a = 100;
console.log(store.state.a, store2.state.a);
  • 通过 store.state 访问状态数据
  • state 数据与组件 data 一样是被追踪的

2.4 在组件中使用 store

// stores/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'

Vue.use(Vuex)

const store = new Vuex.Store({
    
    
    state
})

export default store;
// stores/state.js
export default () => ({
    
    
    title: 'CSDN',
    content: 'Web前端工程师'
})
<template>
  <div class="home">
    <h2>{
   
   {title}}</h2>
    <div>{
   
   {content}}</div>
  </div>
</template>

<script>
import store from '@/stores'
export default {
     
     
  name: 'home',
  data() {
     
     
    return {
     
     
      title: store.state.title,
      content: store.state.content
    }
  }
}
</script>

问题:

state 的更新并不会更新视图

解决

使用 computed

<template>
  <div class="home">
    <h2>{
   
   {title}}</h2>
    <div>{
   
   {content}}</div>
  </div>
</template>

<script>
import store from '@/stores'
export default {
     
     
  name: 'home',
  computed: {
     
     
    title() {
     
     
      return store.state.title
    },
    content() {
     
     
      return store.state.content
    }
  }
}
</script>

2.5 store 配置

如果每个组件在使用 store 的时候都 import 会比较繁琐,这个时候,我们通过 vuex 提供的 store 选项把 store 对象注入到 vue 的原型上

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/stores'

Vue.config.productionTip = false

new Vue({
    
    
  router,
  store,
  render: h => h(App)
}).$mount('#app')

配置注入后,我们就可以在组件实例中使用 this.$store 来访问 store 对象了

<template>
  <div class="home">
    <h2>{
   
   {title}}</h2>
    <div>{
   
   {content}}</div>
  </div>
</template>

<script>
// import store from '@/stores' // 可以去掉了
export default {
     
     
  name: 'home',
  computed: {
     
     
    title() {
     
     
      return this.$store.state.title
    },
    content() {
     
     
      return this.$store.state.content
    }
  }
}
</script>

我们从仓库当中拉取数据与当前组件当中的计算属性一一对应,之前的案例是常规写法,在vue当中,也提供了一些简化式的写法,即我们马上学习的辅助函数。 =>

理解为帮助我们做一些事情的函数,因此称为辅助函数。

下面我们学习的辅助函数 mapState,会帮助我们生成诸如计算属性与仓库数据一一对应的关系。

2.6 使用辅助函数 mapState

当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键,通常我们把 storestate 通过 mapState 函数映射到组件的 computed

<template>
  <div class="home">
    <h2>{
   
   {title}}</h2>
    <div>{
   
   {content}}</div>
  </div>
</template>

<script>
import {
     
     mapState} from 'vuex'

export default {
     
     
  name: 'home',
  computed: mapState([
    'title','content'
  ])
}
</script>

通过对象方式进行映射

场景:当组件中已有与 store 同名的数据名称

<template>
  <div class="home">
    <h1>{
   
   {title}}</h1>
    <h2>{
   
   {subTitle}}</h2>
    <div>{
   
   {content}}</div>
  </div>
</template>

<script>
import {
     
     mapState} from 'vuex'

export default {
     
     
  name: 'home',
  data() {
     
     
    return {
     
     title: 'Vuex'}
  },
  computed: mapState({
     
     
      subTitle: 'title',
      content: ({
     
     content}) => content.length <= 12 ? content : content.substring(0, 12) + '......'
  })
}
</script>

2.6.1 官网实例与注意事项

// 在单独构建的版本中辅助函数为 Vuex.mapState
import {
    
     mapState } from 'vuex'

export default {
    
    
  // ...
  computed: mapState({
    
    
    // 箭头函数可使代码更简练
    count: state => state.count,

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

    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
    
    
      return state.count + this.localCount
    }
  })
}

当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

2.6.2 example01

2.6.2.1 example01-1

home组件中注释掉之前计算属性的代码,调用mapState函数 =>

传数组进去,把itemsn传进去

computed: mapState(['items', 'n']), <=>

效果与之前一样,但是写法更简便 =>

它实际就是帮助我们生成 =>

        computed: {
    
    
            items() {
    
    
                return this.$store.state.items;
            },
            n() {
    
    
                return this.$store.state.n;
            }
        },

\app\src\views\Home.vue

<template>
    <div>
        <h2>商品列表 - {
   
   {n}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     

            }
        },

        filters: {
     
     
            RMB
        },


        // computed: {
     
     
        //     items() {
     
     
        //         return this.$store.state.items;
        //     },
        //     n() {
     
     
        //         return this.$store.state.n;
        //     }
        // },

        computed: mapState(['items', 'n']),

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

效果是一样可以实现的。

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.09
Branch: branch02

commit description:a0.09(example01-1—将计算属性的数据以数组传入mapState)

tag:a0.09

2.6.2.1.1 mapState原理

mapState函数就是用来生成上面的 computed中的结构

=>

我们大致写一下原理

=>

参数是一个数组,首先生成一个对象,然后会帮助我们循环数组,对象里是包含一个个函数,函数的名称就是对象的key,函数里返回的是仓库下state底下的数据对象与这个对象的key一一对应,最终生成此对象结构

    function myMapState(arr) {
    
    
        let fns = {
    
    };
        for (let v of arr) {
    
    
            fns[v] = function() {
    
    
                return this.$store.state[v];
            }
        }
    }

这个函数有些时候,如computed中的名字与$store.state下的名字同名的时候还好,但是如果不同名,假设data中有一个n属性 => computed: mapState(['items', 'n']),

<template>
    <div>
        <h2>商品列表 - {
   
   {n}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n:''
            }
        },

        filters: {
     
     
            RMB
        },
        

        computed: mapState(['items', 'n']),

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.10
Branch: branch02

commit description:a0.10(将计算属性的数据以数组传入mapState与data重名会报错)

tag:a0.10

2.6.2.2 example01-2

为了解决这个问题,可以mapState定义成kay:value的对象形式

items 不需要起别名的话,名字不用动 => keyvalue一样即可

n值换一下 => key: stateN

        computed: mapState({
    
    
            stateN: 'n',
        }),

=>

<template>
    <div>
        <h2>商品列表 - {
   
   {n}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200
            }
        },

        filters: {
     
     
            RMB
        },


        computed: mapState({
     
     
            items: 'items',
            stateN: 'n',
        }),

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.11
Branch: branch02

commit description:a0.11(example01-2—解决mapState同名情况,传kay => value形式的对象)

tag:a0.11

其实mapState定义成kay:value的对象形式,就是为起别名的,当希望mapState中数据的名称与组件当中的某个计算属性的名称不一致的时候,可采用该方式。

2.6.2.3 example01-3

除了这种方式外,还有另外一种方式 =>

函数式,即回调函数,它会有一个参数,这个参数其实就是接收到的所有state

假设我们要给仓库中的n做一个处理,在进入组件后都乘10 =>

        computed: mapState({
    
    
            items: 'items',
            stateN(state) {
    
    
                return state.n * 10;
            }
        }),

=>

<template>
    <div>
        <h2>商品列表 - {
   
   {n}} -{
   
   {stateN}}- {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200
            }
        },

        filters: {
     
     
            RMB
        },


        computed: mapState({
     
     
            items: 'items',
            stateN(state) {
     
     
                return state.n * 10;
            }
        }),

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.12
Branch: branch02

commit description:a0.12(example01-3—解决mapState同名情况,用回调函数解决)

tag:a0.12

2.6.2.4 example01-4

还有一种情况,假设组件当中本来就有计算属性,但是要从mapState也取出计算属性,这个时候希望mapState生成的计算属性与当前原有的计算属性进行合并。比如原先计算属性有个值为valmapState返回值是一个对象,因此需要对对象解构,然后在与computed合并。

        computed:{
    
    
            val() {
    
    
                return 'CSDN';
            },
            ...mapState({
    
    
                items: 'items',
                stateN(state) {
    
    
                    return state.n * 10;
                }
            })
        },

=>

<template>
    <div>
        <h2>商品列表 - {
   
   {val}} - {
   
   {n}} - {
   
   {stateN}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200
            }
        },

        filters: {
     
     
            RMB
        },


        computed:{
     
     
            val() {
     
     
                return 'CSDN';
            },
            ...mapState({
     
     
                items: 'items',
                stateN(state) {
     
     
                    return state.n * 10;
                }
            })
        },

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.13
Branch: branch02

commit description:a0.13(example01-4—mapState与computed同时存在可合并)

tag:a0.13

2.7 使用扩展运算符组合

mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?

通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。

但是自从有了对象展开运算符,我们可以极大地简化写法:

通过对象扩展运算符,可以把 mapState 返回的 state 属性与组件已有计算属性进行融合

<script>
import {
     
     mapState} from 'vuex'

export default {
     
     
  computed: {
     
     
      computed() {
     
     /.../},
       // 使用对象展开运算符将此对象混入到外部对象中
      ...mapState({
     
     
          // ...
      })
  }
}
</script>

2.8 组件仍然保有局部状态

使用 Vuex 并不意味着你需要将所有的状态放入Vuex。虽然将所有的状态放到 Vuex会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

3. getters

有时候我们需要从 store 中的 state 中派生出一些状态,类似组件的 datacomputedstore 也提供了一个 getters 对象来处理派生数据

computed: {
    
    
  doneTodosCount () {
    
    
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex允许我们在 store中定义**“getter”(**可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接受 state作为其第一个参数:

const store = new Vuex.Store({
    
    
  state: {
    
    
    todos: [
      {
    
     id: 1, text: '...', done: true },
      {
    
     id: 2, text: '...', done: false }
    ]
  },
  getters: {
    
    
    doneTodos: state => {
    
    
      return state.todos.filter(todo => todo.done)
    }
  }
})

3.1 getters 函数

与组件属性一样,我们是通过定义一个函数的形式来返回派生数据的,getters 函数接受两个参数

  • 第一个参数:state 对象
  • 第二个参数:getters 对象

3.2 通过属性访问

同样的,与组件计算属性一样,默认是通过属性的方式去访问 getters 中的数据的,这种方式与组件的计算属性一样,也是会缓存结果的。

Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter也可以接受其他 getter作为第二个参数:

getters: {
    
    
  // ...
  doneTodosCount: (state, getters) => {
    
    
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1

我们可以很容易地在任何组件中使用它:

computed: {
    
    
  doneTodosCount () {
    
    
    return this.$store.getters.doneTodosCount
  }
}

注意,getter 在通过属性访问时是作为 Vue的响应式系统的一部分缓存其中的。

3.3 通过方法访问

我们还可以通过闭包函数的形式返回一个函数,来实现给 getters 函数传参,需要注意的是这种方式不支持结果缓存。

你也可以通过让getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

getters: {
    
    
  // ...
  getTodoById: (state) => (id) => {
    
    
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

3.4 使用辅助函数 mapGetters

mapState 函数类似,通常映射到组件的 computed 上。

mapGetters 辅助函数仅仅是将 store中的getter 映射到局部计算属性:

import {
    
     mapGetters } from 'vuex'

export default {
    
    
  // ...
  computed: {
    
    
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }
}

如果你想将一个getter属性另取一个名字,使用对象形式:

...mapGetters({
    
    
  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

3.5 example02

我就不演示辅助函数,它比较简单。 =>

实现找出 超过500的数据

3.5.1 example02-1

app\src\store\index.js

    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return state.items.filter( item => item.price > 50000 );
        }
    },

app\src\views\Home.vue

        computed: {
    
    
            val() {
    
    
                return 'CSDN';
            },
            ...mapState({
    
    
                stateN(state) {
    
    
                    return state.n * 10;
                }
            }),
            items() {
    
    
                console.log(this.$store.getters.than500);
            }
        }, 

在这里插入图片描述

computed: {
    
    
            val() {
    
    
                return 'CSDN';
            },
            ...mapState({
    
    
                stateN(state) {
    
    
                    return state.n * 10;
                }
            }),
            items() {
    
    
                return this.$store.getters.than500;
            }
        }, 

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.14
Branch: branch02

commit description:a0.14(example02-1—实现找出 超过500的数据)

tag:a0.14

3.5.2 example02-2

有的时候需要传参,我们不定死必须大于500,而是自定义。

原始数据与派生数据不一样,原始数据该是怎样就怎样,而派生数据是根据条件自动生成,默认情况就和计算属性一样,直接通过以上的方式调用即可,而还有一种形式是根据某种参数或数据去改变这个值。

如在Home组件上有一个input框,我们希望实现根据input框里的值,过滤出满足这个条件的数据。

=> getters 返回一个函数 => this.$store.getters.than500 里存就是一个函数(这其实相当于函数引用或者函数指针)

app\src\store\index.js

 
    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return function(price = 0) {
    
    
                return state.items.filter( item => item.price > price );
            }
        }
    },

app\src\views\Home.vue

        computed: {
    
    
            val() {
    
    
                return 'CSDN';
            },
            ...mapState({
    
    
                stateN(state) {
    
    
                    return state.n * 10;
                }
            }),
            items() {
    
    
                let rs = this.$store.getters.than500(500000); // 分为单位,实际5000元
                console.log(rs);
                return rs;
            }
        },

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.15
Branch: branch02

commit description:a0.15(example02-2—实现找出 超过自定义数值的数据)

tag:a0.15

4. mutations

更改 Vuexstore中的状态的唯一方法是提交 mutation(类似 redux中的 action + reducer),Vuex 中的mutation非常类似于事件:每个 mutation都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)

mutation 中的函数不要直接调用

这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state作为第一个参数:

const store = new Vuex.Store({
    
    
  state: {
    
    
    count: 1
  },
  mutations: {
    
    
    increment (state) {
    
    
      // 变更状态
      state.count++
    }
  }
})

你不能直接调用一个 mutation handler。这个选项更像是事件注册:“当触发一个类型为 incrementmutation时,调用此函数。”要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:

store.commit('increment')

4.1 提交(载荷Payload)

store.commit(type, payload)
// or
store.commit({
    
    
    type: ...,
    payload: ...
})

type

要提交的 mutation 回调函数名称

payload

载荷:提交的额外数据,任意格式

你可以向 store.commit 传入额外的参数,即mutation载荷(payload)

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

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

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

4.2 Mutation 需遵守 Vue 的响应规则

既然 Vuexstore中的状态是响应式的,那么当我们变更状态时,监视状态的Vue组件也会自动更新。这也意味着 Vuex中的 mutation 也需要与使用Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。
  2. 当需要在对象上添加新属性时,你应该
  • 使用 Vue.set(obj, 'newProp', 123), 或者

  • 以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:

    state.obj = {
          
           ...state.obj, newProp: 123 }
    

4.3 使用常量替代 Mutation 事件类型

使用常量替代mutation事件类型在各种Flux实现中是很常见的模式。这样可以使 linter之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个app包含的mutation一目了然:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import {
    
     SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
    
    
  state: {
    
     ... },
  mutations: {
    
    
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
    
    
      // mutate state
    }
  }
})

用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。

4.4 mutation 函数

mutation 中的函数被 commit 执行的时候,接收两个参数

  • 第一个参数:state 对象
  • 第二个参数: commit 提交的 payload

mutation 函数中,我们就可以通过 state 对象进行状态数据的修改

4.5 使用辅助函数 mapMutations

mapMutations 函数的使用与 mapStatemapGetters 类似,但是其一般是把组件的 methods 映射为 storemutationscommit 调用

4.6 mutation 函数必须是同步的

commit 方法没有返回值

一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:

mutations: {
    
    
  someMutation (state) {
    
    
    api.callAsyncMethod(() => {
    
    
      state.count++
    })
  }
}

现在想象,我们正在 debug一个 app并且观察 devtool中的 mutation 日志。每一条 mutation 被记录,devtools都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation中的异步函数中的回调让这不可能完成:因为当 mutation触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

4.7 在组件中提交 Mutation

你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

import {
    
     mapMutations } from 'vuex'

export default {
    
    
  // ...
  methods: {
    
    
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
    
    
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

mutation 中混合异步调用会导致你的程序很难调试。

例如,当你调用了两个包含异步回调的 mutation来改变状态,你怎么知道什么时候回调和哪个先回调呢?

这就是为什么我们要区分这两个概念。在 Vuex 中,mutation 都是同步事务

store.commit('increment')
// 任何由 "increment" 导致的状态变更都应该在此刻完成。

为了处理异步操作,让我们来看一看[Action]

4.8 实例

4.8.1 example03

4.8.1.1 example03-1

现在我们不再定死数据了,数据从后端获取,向后端发送请求。

\app\src\apis\index.js

import axios from 'axios'
import URLS from './URLS'
 
// export async function getItems() {
    
    
export async function getItems(sort='desc') {
    
    
    let rs = await axios({
    
    
        url: URLS.ITEMS + '?sort=' + sort
        // url: URLS.ITEMS
    });
 
    return rs;
}
 
 
export async function getItem(id) {
    
    
 
    let rs = await axios({
    
    
        url: URLS.ITEM + '/' + id
    });
 
    return rs;
}

我们在Home组件的created周期中,我们可以调用之前写的apis里发送请求 =>

let rs = await apis.getItems()原先是这样请求数据后,将其赋值给data。而现在需要首先存给仓库,因此我们需要在仓库中写一个方法mutation => async updateItems(state, payload){state.items = payload;}

\app\src\views\Home.vue

<template>
    <div>
        <h2>商品列表 - {
   
   {val}} - {
   
   {n}} - {
   
   {stateN}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200
            }
        },

        filters: {
     
     
            RMB
        },


        computed:{
     
     
            val() {
     
     
                return 'CSDN';
            },
            ...mapState({
     
     
                stateN(state) {
     
     
                    return state.n * 10;
                }
            }),
            items() {
     
     
                // let rs = this.$store.getters.than500(500000); // 分为单位,实际5000元
                let rs = this.$store.state.items;
                console.log(rs);
                return rs;
            }
        },

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        },

        async created() {
     
     
            let rs = await apis.getItems()
            this.$store.commit('updateItems', rs.data);
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

\app\src\store\index.js

    mutations: {
    
    
        changeN(state, payload) {
    
    
            state.n = payload;
        },
 
        addItem(state, payload) {
    
    
            // state.items = [{
    
    
            //     id: ++maxId,
            //     ...payload
            // }, ...state.items];
        },
 
        async updateItems(state, payload) {
    
    
            state.items = payload;
        },
    }

效果是一样的,这个时候是先发请求获取数据,再向仓库发请求更新仓库 =>

Home组件中computed属性中的items属性也跟着计算更新了。

这里虽然是在组件中请求了数据实际还得走仓库绕一绕。

在这里插入图片描述

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.16
Branch: branch02

commit description:a0.16(example03-1—从后端请求数据)

tag:a0.16

4.8.1.2 example03-2

但是以上的做法并不值得推荐,这样调用apis的行为可能不止一个页面去用,要是成千个页面,你怎么整?

不可能每个地方都写吧?就相当于当初我们把axios请求封装起来一样,不可能仅仅一处用,为了用起来方便,我们才去封装的。

同样也许很多页面都要调用这些数据,然后再更新仓库数据,我们最好把请求也放到仓库中去做,封装起来。

=>

Home组件的created中只需要提交的更新请求动作(可以带一个sort参数过去)即可,剩下全封装在仓库中。

\app\src\views\Home.vue

<template>
    <div>
        <h2>商品列表 - {
   
   {val}} - {
   
   {n}} - {
   
   {stateN}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200
            }
        },

        filters: {
     
     
            RMB
        },


        computed:{
     
     
            val() {
     
     
                return 'CSDN';
            },
            ...mapState({
     
     
                items:'items',
                stateN(state) {
     
     
                    return state.n * 10;
                }
            })
        },

        methods: {
     
     
            addItem() {
     
     
                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    this.$store.commit('addItem', {
     
     
                        "name": val,
                        "vendor":"Apple",
                        "price":1949900
                    })
                }
            }
        },

        async created() {
     
     
            this.$store.commit('updateItems', {
     
     sort: 'desc'});
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

\app\src\store\index.js

import Vue from 'vue';
import Vuex from 'vuex';
import * as apis from '@/apis';

Vue.use(Vuex);

// let items = [{"id":3,"name":"Macbook Pro 15.4","vendor":"Apple","price":1949900},{"id":4,"name":"Apple iMac","vendor":"Apple","price":1629900},{"id":9,"name":"游戏本2019款","vendor":"XiaoMi","price":879900},{"id":6,"name":"Apple Watch Series 4","vendor":"Apple","price":599900},{"id":1,"name":"iPhone XR","vendor":"Apple","price":542500},{"id":11,"name":"HUAWEI P30 Pro","vendor":"HuaWei","price":498800},{"id":2,"name":"Apple iPad Air 3","vendor":"Apple","price":377700},{"id":10,"name":"HUAWEI P30","vendor":"HuaWei","price":368800},{"id":7,"name":"小米9","vendor":"XiaoMi","price":259900},{"id":12,"name":"华为平板 M6 10.8英寸","vendor":"HuaWei","price":229900},{"id":16,"name":"Redmi K20","vendor":"XiaoMi","price":199900},{"id":13,"name":"HUAWEI WATCH GT","vendor":"HuaWei","price":128800},{"id":5,"name":"Apple Magic Mouse","vendor":"Apple","price":72900},{"id":8,"name":"小米手环4","vendor":"XiaoMi","price":16900}];
//
// let maxId = items.reduce(function(maxId, item ) {
    
    
//     return item.id > maxId ? item.id : maxId;
// }, 0);

let items = [];

let store = new Vuex.Store({
    
    
    state: {
    
    
        n: 10,
        items
    },

    mutations: {
    
    
        changeN(state, payload) {
    
    
            state.n = payload;
        },

        addItem(state, payload) {
    
    
        },

        async updateItems(state, payload) {
    
    
            let rs = await apis.getItems(payload);
            state.items = rs.data;
        },
    },

    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return function(price = 0) {
    
    
                return state.items.filter( item => item.price > price );
            }
        }
    }
});


export default store;

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.17
Branch: branch02

commit description:a0.17(example03-2—向后端请求数据封装进仓库里)

tag:a0.17

4.8.1.3 example03-3

看起来这样做是没有任何问题的,但是实际上还是有一些问题。

\server\app.js

const Koa = require('koa');
const KoaRouter = require('koa-router');
const KoaBodyParser = require('koa-bodyparser')
 
let datas = {
    
    
    items: require('./data/items.json'),
    users: require('./data/users.json')
}
 
const app = new Koa();
 
let currentId = 20;
 
app.use( async (ctx, next) => {
    
    
    await next();
} );
 
app.use(KoaBodyParser());
 
const router = new KoaRouter();
 
router.get('/', async ctx => {
    
    
    ctx.body = 'api';
});
 
router.get('/items', async ctx => {
    
    
    let sort = ctx.request.query.sort || 'desc';
    let items = datas.items.sort((a, b) => sort === 'asc'  ? a.price - b.price : b.price - a.price);
 
    ctx.body = items;
});
 
router.get('/item/:id', async ctx => {
    
    
    let id = Number(ctx.params.id);
    let item = datas.items.find(item => item.id === id);
 
    if (!item) {
    
    
        ctx.throw(404, '没有该商品信息');
        return;
    }
 
    ctx.body = item;
 
});
 
router.post('/add', async ctx => {
    
    
    let {
    
    name} = ctx.request.body;
 
    if (name === '') {
    
    
        ctx.body = {
    
    
            code: 1,
            message: '商品名称不能为空'
        }
        return;
    }
 
    let newData = {
    
    
        id: currentId++,
        name
    };
 
    datas.items.unshift(newData);
 
    ctx.body = {
    
    
        code: 0,
        message: '提交成功',
        data: newData
    }
})
 
app.use( router.routes() );
 
app.listen(7777);

我们在对接后端接口apis文件中写一个提交接口

\app\src\apis\URLS.js

export default {
    
    
    'ITEMS': '/api/items',
    'ITEM': '/api/item',
    'ADD_ITEM': '/api/add'
}

\app\src\apis\index.js

import axios from 'axios'
import URLS from './URLS'

// export async function getItems() {
    
    
export async function getItems(sort='desc') {
    
    
    let rs = await axios({
    
    
        url: URLS.ITEMS + '?sort=' + sort
        // url: URLS.ITEMS
    });

    return rs;
}


export async function getItem(id) {
    
    

    let rs = await axios({
    
    
        url: URLS.ITEM + '/' + id
    });

    return rs;
}

export async function postItem(data) {
    
    
    let rs = await axios({
    
    
        method: 'post',
        url: URLS.ADD_ITEM,
        data
    });
    return rs;
}

Home组件中,触发提交只需要向仓库提交一个名称即可。

仓库的mutations中的addItem动作中,调用后端apis的提交接口,然后判断后端请求的返回值是否出错(code0成功),成功之后再更新进仓库。

\app\src\views\Home.vue

        methods: {
    
    
            addItem() {
    
    
 
                let val = this.$refs.input.value;
 
                if (val !== '') {
    
    
                    this.$store.commit('addItem',{
    
    'name':val});
                }
            }
        }

test01\app\src\store\index.js

import Vue from 'vue';
import Vuex from 'vuex';
import * as apis from '@/apis';

Vue.use(Vuex);

let items = [];

let store = new Vuex.Store({
    
    
    state: {
    
    
        n: 10,
        items
    },

    mutations: {
    
    
        changeN(state, payload) {
    
    
            state.n = payload;
        },

        async addItem(state, payload) {
    
    
            let rs = await apis.postItem(payload);
            if (!rs.data.code) {
    
    
                state.items.unshift(rs.data.data);
            }
        },

        async updateItems(state, payload) {
    
    
            let rs = await apis.getItems(payload);
            state.items = rs.data;
        },
    },

    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return function(price = 0) {
    
    
                return state.items.filter( item => item.price > price );
            }
        }
    }
});


export default store;

做前后端交互必须检查network,在network中检查请求是成功了。主要看Header中的提交地址有没有问题?提交方法、状态码、头信息、以及带过去的数据是否没问题。再看Response返回值,是否正确。紧接着再刷新页面,数据也是存在着的。

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.18
Branch: branch02

commit description:a0.18(example03-3—向仓库添加数据并更新到后端)

tag:a0.18

4.8.1.4 example03-4

好像看起来似乎是没有任何问题了,但是实际上提交是有可能会失败的。

后端判断添加数据排除空格 =>

router.post('/add', async ctx => {
    
    
    let {
    
    name} = ctx.request.body;
 
    if (name.trim() === '') {
    
    
        ctx.body = {
    
    
            code: 1,
            message: '商品名称不能为空'
        }
        return;
    }
 
    let newData = {
    
    
        id: currentId++,
        name
    };
 
    datas.items.unshift(newData);
 
    ctx.body = {
    
    
        code: 0,
        message: '提交成功',
        data: newData
    }
})
 

我们现在输入一些空格添加数据,后端肯定返回失败。 => 实现前端显示错误提示

首先点击提交按钮,触发仓库addItem请求动作,由仓库的mutationsaddItem执行,并向后端发送添加数据请求,如果提交成功,则更新仓库数据,否则返回后端response的数据。

即在Home组件的addItem中获取提交数据的结果,因此我们需要addItem上添加asyncawait

可能有人问为啥不直接在仓库下index中处理错误结果呢,还要返回给Home组件呢?

希望我们操作完数据并希望能在界面上有所体现的,所以界面组件是需要返回值做判断的。同时仓库中是不能操作组件的,会不太方便的,并且这样增加了组件间耦合度,不利于代码复用的。

其实将UI的逻辑和数据逻辑全部混合在一起,是不推荐的,数据处理数据的,要是需要界面产生交互,应该由数据去通知UI界面,这样的设计更为理想一些。

\app\src\store\index.js

import Vue from 'vue';
import Vuex from 'vuex';
import * as apis from '@/apis';

Vue.use(Vuex);

// let items = [{"id":3,"name":"Macbook Pro 15.4","vendor":"Apple","price":1949900},{"id":4,"name":"Apple iMac","vendor":"Apple","price":1629900},{"id":9,"name":"游戏本2019款","vendor":"XiaoMi","price":879900},{"id":6,"name":"Apple Watch Series 4","vendor":"Apple","price":599900},{"id":1,"name":"iPhone XR","vendor":"Apple","price":542500},{"id":11,"name":"HUAWEI P30 Pro","vendor":"HuaWei","price":498800},{"id":2,"name":"Apple iPad Air 3","vendor":"Apple","price":377700},{"id":10,"name":"HUAWEI P30","vendor":"HuaWei","price":368800},{"id":7,"name":"小米9","vendor":"XiaoMi","price":259900},{"id":12,"name":"华为平板 M6 10.8英寸","vendor":"HuaWei","price":229900},{"id":16,"name":"Redmi K20","vendor":"XiaoMi","price":199900},{"id":13,"name":"HUAWEI WATCH GT","vendor":"HuaWei","price":128800},{"id":5,"name":"Apple Magic Mouse","vendor":"Apple","price":72900},{"id":8,"name":"小米手环4","vendor":"XiaoMi","price":16900}];
//
// let maxId = items.reduce(function(maxId, item ) {
    
    
//     return item.id > maxId ? item.id : maxId;
// }, 0);

let items = [];

let store = new Vuex.Store({
    
    
    state: {
    
    
        n: 10,
        items
    },

    mutations: {
    
    
        changeN(state, payload) {
    
    
            state.n = payload;
        },

        async addItem(state, payload) {
    
    
            let rs = await apis.postItem(payload);
            if (!rs.data.code) {
    
    
                state.items.unshift(rs.data.data);
            }
        },

        async updateItems(state, payload) {
    
    
            let rs = await apis.getItems(payload);
            state.items = rs.data;
        },
    },

    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return function(price = 0) {
    
    
                return state.items.filter( item => item.price > price );
            }
        }
    }
});


export default store;

\app\src\views\Home.vue

        methods: {
    
    
            async addItem() {
    
    

                let val = this.$refs.input.value;

                if (val !== '') {
    
    
                    let rs = await this.$store.commit('addItem',{
    
    'name':val});
                    console.log(rs);
                }
            }
        }

打印的结果是undefined,说明Home组件中的this.$store.commit('addItem',{'name':val})并不会返回任何东西。 =>

这是因为store中的mutations中的函数不对异步代码进行处理。

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.19
Branch: branch02

commit description:a0.19(example03-4—store中的mutations中的函数不对异步代码进行处理)

tag:a0.19

4.8.1.5 小结

commit的时候,其实就是在给commit函数传递一个函数名,它会在mutations中寻找这个函数名,并把它执行了。实际函数的执行,是由commit内部方法决定的,并且commit这个方法没有加async,即commit函数并没有

=> return await mutations[fnName](); => 即commit方法只执行这些函数,并不会返回任何值,可以理解为它除了帮忙调用函数外,其他任何事都不做。 => commit不会对异步任务做任何处理,即我们刚写的异步任务在commit中就不会等待,因此我们在Home组件中是获取不到commit的返回值的。

function commit(fnName) {
    
    
    mutations[fnName]();
}

实际Mutations是允许包含异步任务的,如果只做处理,不做任何返回的话,其实大部分情况下是没有任何问题的

=> 这个时候就需要actins来解决问题了

5. actions

action 中的函数与 mutation 中的函数类似,但是它主要用来进行异步任务的处理,然后通过提交 mutation 来修改 state

注意:action 中的函数不要直接修改 state

不同(mutation )在于:

  • Action提交的是 mutation,而不是直接变更状态。
  • Action可以包含任意异步操作。

让我们来注册一个简单的 action

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

Action 函数接受一个与 store 实例具有相同方法和属性的context对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 stategetters。当我们在之后介绍到 Modules时,你就知道 context对象为什么不是store实例本身了。

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):

actions: {
    
    
  increment ({
    
     commit }) {
    
    
    commit('increment')
  }
}

5.1 提交

Action 通过 store.dispatch 方法触发:

store.dispatch('increment')

乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在action内部执行异步操作:

actions: {
    
    
  incrementAsync ({
    
     commit }) {
    
    
    setTimeout(() => {
    
    
      commit('increment')
    }, 1000)
  }
}

Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发
store.dispatch('incrementAsync', {
    
    
  amount: 10
})

// 以对象形式分发
store.dispatch({
    
    
  type: 'incrementAsync',
  amount: 10
})

来看一个更加实际的购物车示例,涉及到调用异步 API分发多重 mutation

actions: {
    
    
  checkout ({
    
     commit, state }, products) {
    
    
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
      products,
      // 成功操作
      () => commit(types.CHECKOUT_SUCCESS),
      // 失败操作
      () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
  }
}

注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)。

store.dispatch(type, payload)
// or
store.dispatch({
    
    
    type: ...,
    payload: ...
})

action 任务需要通过 dispatch 方法来提交(派发),与 commit 类似

dispatch 方法有返回值,且一定返回一个 promise 对象

5.1.1 在组件中提交

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

import {
    
     mapActions } from 'vuex'

export default {
    
    
  // ...
  methods: {
    
    
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
    
    
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

5.2 组合 Action

Action通常是异步的,那么如何知道 action 什么时候结束呢?

更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的action的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise

actions: {
    
    
  actionA ({
    
     commit }) {
    
    
    return new Promise((resolve, reject) => {
    
    
      setTimeout(() => {
    
    
        commit('someMutation')
        resolve()
      }, 1000)
    })
  }
}

现在你可以:

store.dispatch('actionA').then(() => {
    
    
  // ...
})

在另外一个 action 中也可以:

actions: {
    
    
  // ...
  actionB ({
    
     dispatch, commit }) {
    
    
    return dispatch('actionA').then(() => {
    
    
      commit('someOtherMutation')
    })
  }
}

最后,如果我们利用 async / await,我们可以如下组合 action

// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
    
    
  async actionA ({
    
     commit }) {
    
    
    commit('gotData', await getData())
  },
  async actionB ({
    
     dispatch, commit }) {
    
    
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

5.3 action 函数

action 中的函数执行的过程中也接受两个参数

  • 第一个参数:store 对象
  • 第二个参数: dispatch 提交的 payload

5.4 使用辅助函数 mapActions

mapMutations 函数类似,把组件的 methods 映射为 storeactionsdispatch 调用

5.5 实例

5.5.1 example04

这里不知你直接调用state了,而是需要调用commit,请求mutation中的方法。 => store.commit('addItem', rs.data.data);

5.5.1.1 利用action发请求的整个流程

ui => dispatch => actions => commit => mutations => state

5.5.1.2 example04-1

\app\src\store\index.js

import Vue from 'vue';
import Vuex from 'vuex';
import * as apis from '@/apis';
 
Vue.use(Vuex);
 
let items = [];
 
let store = new Vuex.Store({
    
    
    state: {
    
    
        n: 10,
        items
    },
 
    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return function(price = 0) {
    
    
                return state.items.filter( item => item.price > price );
            }
        }
    },
 
    mutations: {
    
    
        changeN(state, payload) {
    
    
            state.n = payload;
        },
 
        // mutations 中的函数不对异步代码进行处理
        addItem(state, payload) {
    
    
            state.items.unshift(payload);
        },
 
        async updateItems(state, payload) {
    
    
            let rs = await apis.getItems(payload);
            state.items = rs.data;
        },
    },
 
    actions: {
    
    
        async addItem(store, payload) {
    
    
            let rs = await apis.postItem(payload);
            if (!rs.data.code) {
    
    
                // action 里面不能直接处理state
                // state.items.unshift(rs.data.data);
                store.commit('addItem', rs.data.data);
            }
            return rs;
        }
    }
});
 
 
export default store;

\app\src\views\Home.vue

<template>
    <div>
        <h2>商品列表 - {
   
   {n}} - {
   
   {stateN}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <span v-show="message">{
   
   {message}}</span>
        <hr>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200,
                message: ''
            }
        },

        filters: {
     
     
            RMB
        },


        computed:{
     
     
            ...mapState({
     
     
                items:'items',
                stateN(state) {
     
     
                    return state.n * 10;
                }
            })
        },

        methods: {
     
     
            async addItem() {
     
     

                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    // 提交是有可能会失败的
                    let rs = await this.$store.dispatch('addItem', {
     
     
                        "name": val
                    });
                    // console.log(rs);
                    if (rs.data.code) {
     
     
                        this.message = rs.data.message;
                    } else {
     
     
                        this.message = '提交成功';
                    }
                    // this.message = '添加失败';
                }
            }
        },

        async created() {
     
     
            this.$store.commit('updateItems', {
     
     sort: 'desc'});
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.20
Branch: branch02

commit description:a0.20(example04-1—利用action解决异步返回问题)

tag:a0.20

5.5.1.2.1 小结

这样就把数据的处理UI处理逻辑分离开了,更容易复用功能,如另外一个页面不需要这样的提示,都封装在仓库的addItem中处理,就难以复用了。所以数据就单独只有数据的处理,不要再做其他工作。

注意storemutations并不是处理不了异步任务,而是不对异步任务的返回进行处理。但其实就算同步任务也是不行的,就理解mutations只负责做事,不返回结果。如希望我们的操作反馈结果的话,这个时候就需要action了。实际同步任务返不返回结果都无所谓,因为我们直接可以在仓库的state中获取了。但是如果异步任务我们也通过此种方式获取,就不是最新的值,因为会存在延迟。

5.5.1.3 example04-2

我们演示一下,在Home加一个按钮 => <button @*click*="getData">按钮</button>

仓库中changeN不涉及异步处理,同步修改 =>

this.$store.commit(‘changeN’, 11)

获取返回值 => console.log(this.$store.state.n)

<template>
    <div>
        <h2>商品列表 - {
   
   {n}} - {
   
   {stateN}} - {
   
   {$store.state.n}}</h2>

        <input type="text" ref="input" /><button @click="addItem">提交</button>
        <span v-show="message">{
   
   {message}}</span>
        <hr>

        <button @click="getData">按钮</button>

        <ul class="item-list">
            <li class="head">
                <span>名称</span>
                <span>价格</span>
                <span>操作</span>
            </li>
            <li v-for="item of items" :key="item.id">
                <span>
                    <router-link :to="{name: 'view', params: {id: item.id}}">{
   
   {item.name}}</router-link>
                </span>
                <span>{
   
   {item.price|RMB}}</span>
                <span>
                    <button>添加到购物车</button>
                </span>
            </li>
        </ul>
    </div>
</template>

<script>
    import * as apis from '@/apis'
    import {
     
     RMB} from "@/filters/RMB";
    import {
     
     mapState} from "vuex";

    export default {
     
     
        name: "Home",

        data() {
     
     
            return {
     
     
                n: 200,
                message: ''
            }
        },

        filters: {
     
     
            RMB
        },


        computed:{
     
     
            ...mapState({
     
     
                items:'items',
                stateN(state) {
     
     
                    return state.n * 10;
                }
            })
        },

        methods: {
     
     
            async addItem() {
     
     

                let val = this.$refs.input.value;

                if (val !== '') {
     
     
                    // 提交是有可能会失败的
                    let rs = await this.$store.dispatch('addItem', {
     
     
                        "name": val
                    });
                    // console.log(rs);
                    if (rs.data.code) {
     
     
                        this.message = rs.data.message;
                    } else {
     
     
                        this.message = '提交成功';
                    }
                    // this.message = '添加失败';
                }
            },
            getData() {
     
     
                this.$store.commit('changeN', 11);
                console.log(this.$store.state.n);
            }
        },

        async created() {
     
     
            this.$store.commit('updateItems', {
     
     sort: 'desc'});
        }
    }
</script>

<style>
    ul {
     
     
        margin: 0;
        padding: 0;
    }

    li {
     
     
        list-style: none;
    }

    .item-list li {
     
     
        padding: 10px;
        display: flex;
        justify-content: space-between;
        height: 30px;
        line-height: 30px;
        border-bottom: 1px dotted #333;
    }
    .item-list li.head {
     
     
        font-weight: bold;
    }
    .item-list li span {
     
     
        min-width: 200px;
    }
</style>

同步虽然在mutations不能有返回值,但是它会同步修改state的,我们直接通过state就可以获取最新的值了。

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.21
Branch: branch02

commit description:a0.21(example04-2—同步任务mutations如何获取返回值)

tag:a0.21

5.5.1.4 example04-3

如果这个修改是异步的就有问题了

app\src\store\index.js

        changeN(state, payload) {
    
    
            // state.n = payload;
            setTimeout(() => {
    
    
                state.n = payload;
            }, 1000);
        },

因为是异步的会有延迟,因此我们第一次取得值不是最新的。

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.22
Branch: branch02

commit description:a0.22(example04-3—异步任务mutations获取返回值出现延迟)

tag:a0.22

5.5.1.5 example04-4

但是如果把它放在actions中 =>

Home.vue

 export default {
    
    
        name: "Home",

        data() {
    
    
            return {
    
    
                n: 200,
                message: ''
            }
        },

        filters: {
    
    
            RMB
        },


        computed:{
    
    
            ...mapState({
    
    
                items:'items',
                stateN(state) {
    
    
                    return state.n * 10;
                }
            })
        },

        methods: {
    
    
            async addItem() {
    
    

                let val = this.$refs.input.value;

                if (val !== '') {
    
    
                    // 提交是有可能会失败的
                    let rs = await this.$store.dispatch('addItem', {
    
    
                        "name": val
                    });
                    // console.log(rs);
                    if (rs.data.code) {
    
    
                        this.message = rs.data.message;
                    } else {
    
    
                        this.message = '提交成功';
                    }
                    // this.message = '添加失败';
                }
            },
            async getData() {
    
    
                let rs = await this.$store.dispatch('changeAsyncN', 11);
                console.log(rs);
                console.log(this.$store.state.n);
            }
        },

        async created() {
    
    
            this.$store.commit('updateItems', {
    
    sort: 'desc'});
        }
    }

app\src\store\index.js

import Vue from 'vue';
import Vuex from 'vuex';
import * as apis from '@/apis';

Vue.use(Vuex);

let items = [];

let store = new Vuex.Store({
    
    
    state: {
    
    
        n: 10,
        items
    },

    getters: {
    
    
        // 派生数据
        than500(state) {
    
    
            return function(price = 0) {
    
    
                return state.items.filter( item => item.price > price );
            }
        }
    },

    mutations: {
    
    
        changeN(state, payload) {
    
    
            state.n = payload;
            // setTimeout(() => {
    
    
            //     state.n = payload;
            // }, 1000);
        },

        // mutations 中的函数不对异步代码进行处理
        addItem(state, payload) {
    
    
            state.items.unshift(payload);
        },

        async updateItems(state, payload) {
    
    
            let rs = await apis.getItems(payload);
            state.items = rs.data;
        },
    },

    actions: {
    
    
        async addItem(store, payload) {
    
    
            let rs = await apis.postItem(payload);
            if (!rs.data.code) {
    
    
                // action 里面不能直接处理state
                // state.items.unshift(rs.data.data);
                store.commit('addItem', rs.data.data);
            }
            return rs;
        },

        async changeAsyncN(store, payload) {
    
    
            return new Promise( (resolve) => {
    
    
                setTimeout(() => {
    
    
                    store.commit('changeN', payload);
                    resolve('changeAsyncN'+payload);
                }, 1000);
            } );
        }
    }
});


export default store;

在这里插入图片描述

参考:https://github.com/6xiaoDi/blog-Vuex-Novice/tree/a0.23
Branch: branch02

commit description:a0.23(example04-4—解决异步任务mutations获取返回值出现延迟)

tag:a0.23

6. Module

这个更多的是基于一种代码组织结构上的辅助。

模块主要用来组织mutationsgettersetteraction的方法,当项目很庞大的时候,可能仓库的数据特别繁琐,为了能让仓库的数据组织有序,看起来结构清晰,这个时候就有了模块的概念,它就相当于命名空间的概念,用模块来相互独立。

注意如果有些数据在模块外,不是直接获取的,而是rootState获取。

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter、甚至是嵌套子模块——从上至下进行同样方式的分割:

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 的状态

6.1 模块的局部状态

对于模块内部的mutationgetter,接收的第一个参数是模块的局部状态对象

const moduleA = {
    
    
  state: () => ({
    
    
    count: 0
  }),
  mutations: {
    
    
    increment (state) {
    
    
      // 这里的 `state` 对象是模块的局部状态
      state.count++
    }
  },

  getters: {
    
    
    doubleCount (state) {
    
    
      return state.count * 2
    }
  }
}

同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

const moduleA = {
    
    
  // ...
  actions: {
    
    
    incrementIfOddOnRootSum ({
    
     state, commit, rootState }) {
    
    
      if ((state.count + rootState.count) % 2 === 1) {
    
    
        commit('increment')
      }
    }
  }
}

对于模块内部的getter,根节点状态会作为第三个参数暴露出来:

const moduleA = {
    
    
  // ...
  getters: {
    
    
    sumWithRootCount (state, getters, rootState) {
    
    
      return state.count + rootState.count
    }
  }
}

6.2 命名空间

默认情况下,模块内部的 actionmutationgetter是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction作出响应。

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

const store = new Vuex.Store({
    
    
  modules: {
    
    
    account: {
    
    
      namespaced: true,

      // 模块内容(module assets)
      state: () => ({
    
     ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
    
    
        isAdmin () {
    
     ... } // -> getters['account/isAdmin']
      },
      actions: {
    
    
        login () {
    
     ... } // -> dispatch('account/login')
      },
      mutations: {
    
    
        login () {
    
     ... } // -> commit('account/login')
      },

      // 嵌套模块
      modules: {
    
    
        // 继承父模块的命名空间
        myPage: {
    
    
          state: () => ({
    
     ... }),
          getters: {
    
    
            profile () {
    
     ... } // -> getters['account/profile']
          }
        },

        // 进一步嵌套命名空间
        posts: {
    
    
          namespaced: true,

          state: () => ({
    
     ... }),
          getters: {
    
    
            popular () {
    
     ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

启用了命名空间的getteraction会收到局部化的 getterdispatchcommit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。

6.2.1 在带命名空间的模块内访问全局内容(Global Assets)

如果你希望使用全局 stategetterrootStaterootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action

若需要在全局命名空间内分发 action 或提交mutation,将 { root: true } 作为第三参数传给 dispatchcommit 即可。

modules: {
    
    
  foo: {
    
    
    namespaced: true,

    getters: {
    
    
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
    
    
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => {
    
     ... }
    },

    actions: {
    
    
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({
    
     dispatch, commit, getters, rootGetters }) {
    
    
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, {
    
     root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, {
    
     root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) {
    
     ... }
    }
  }
}

6.2.2 在带命名空间的模块注册全局 action

若需要在带命名空间的模块注册全局action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{
    
    
  actions: {
    
    
    someOtherAction ({
    
    dispatch}) {
    
    
      dispatch('someAction')
    }
  },
  modules: {
    
    
    foo: {
    
    
      namespaced: true,

      actions: {
    
    
        someAction: {
    
    
          root: true,
          handler (namespacedContext, payload) {
    
     ... } // -> 'someAction'
        }
      }
    }
  }
}

6.2.3 带命名空间的绑定函数

当使用 mapState, mapGetters, mapActionsmapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

computed: {
    
    
  ...mapState({
    
    
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
    
    
  ...mapActions([
    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:

computed: {
    
    
  ...mapState('some/nested/module', {
    
    
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
    
    
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

而且,你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import {
    
     createNamespacedHelpers } from 'vuex'

const {
    
     mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
    
    
  computed: {
    
    
    // 在 `some/nested/module` 中查找
    ...mapState({
    
    
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    
    
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

6.2.4 给插件开发者的注意事项

如果你开发的插件(Plugin)提供了模块并允许用户将其添加到 Vuex store,可能需要考虑模块的空间名称问题。对于这种情况,你可以通过插件的参数对象来允许用户指定空间名称:

// 通过插件的参数对象得到空间名称
// 然后返回 Vuex 插件函数
export function createPlugin (options = {
    
    }) {
    
    
  return function (store) {
    
    
    // 把空间名字添加到插件模块的类型(type)中去
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}

6.3 模块动态注册

store 创建之后,你可以使用 store.registerModule 方法注册模块:

import Vuex from 'vuex'

const store = new Vuex.Store({
    
     /* 选项 */ })

// 注册模块 `myModule`
store.registerModule('myModule', {
    
    
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
    
    
  // ...
})

之后就可以通过 store.state.myModulestore.state.nested.myModule 访问模块的状态。

模块动态注册功能使得其他Vue 插件可以通过在 store中附加新模块的方式来使用 Vuex 管理状态。例如,vuex-router-sync 插件就是通过动态注册模块将vue-routervuex结合在一起,实现应用的路由状态管理。

你也可以使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建store 时声明的模块)。

注意,你可以通过 store.hasModule(moduleName) 方法检查该模块是否已经被注册到store

6.3.1 保留 state

在注册一个新 module时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })

当你设置 preserveState: true 时,该模块会被注册,actionmutationgetter会被添加到 store 中,但是state不会。这里假设storestate 已经包含了这个 modulestate 并且你不希望将其覆写。

6.4 模块重用

有时我们可能需要创建一个模块的多个实例,例如:

如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。

实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):

const MyReusableModule = {
    
    
  state: () => ({
    
    
    foo: 'bar'
  }),
  // mutation, action 和 getter 等等...
}

7. 重点说明

其实对于中小应用而言,仓库管理不常用,因为如何判断这个数据是否常用呢?

它有两个参考点 =>

多组件共享(且共享麻烦的情况下) =>

如果数据在多个组件共享,同时很麻烦,如同级组件或嵌套过多的父子组件的子孙节点,通过组件的props传递数据麻烦的情况下。

多页面共享 =>

路由跳转的过程中也需要共享数据

如当前的Home组件,获取这些数据存在仓库中,如果有分页功能,点击每页都需要向后端请求数据,然后又把它往仓库中存储,这样是否合理呢?

本质上没啥问题,但是实际上产生了另外的问题 =>

数据的缓存性

如我们跳回第一页,之前已经获取过这个数据了,现在实际不需要再向后端请求了,那你可能想到直接去仓库中获取那一页的数据就好了,这样可以节省请求。但是实际上这会造成另一个问题,它的时效性问题,除非这个数据变更不太大的情况下可以这么去做,实际这里就是把仓库作为了缓存容器使用了。

=>

先判断你要的数据存不存在,如果存在就直接去仓库中取,如果不存在就去后端拉取过来,再更新仓库即可,最后前端页面再去使用即可。这样实际上,带来很大的问题就是数据的维护性极差,这个时候去处理缓存的问题及缓存命中、缓存时效性的问题。

一般情况下,看应用的需求再来决定用不用仓库,一般它存储当前用户登录状态,这个时候做页面和路由跳转的时候就会很方便了。还有一些比较常用和通用的数据也会常常存在仓库中,像比较独立性的数据。

如做商城的时候,商品的量实际非常大,通过仓库去维护这么大的数据实际并不合适,但可以优化,比如只存前5页的数据,而后5页的数据直接请求就好了(一般用户都会浏览前几页,很少有人喜欢倒着浏览,甚至超过5页浏览的人也不太多),因此可以把前5页缓存到仓库中。可以在5分钟内在仓库中取,否则的话从后端去取然后更新仓库。

一定注意不要什么数据都扔到仓库中,这样维护起来的成本很大很麻烦。

考虑到在blog中不好体现代码更改的位置,小迪才用github托管代码,大家可以查看github,看到详细版本修改过程,搭配博客学习。



(后续待补充)

猜你喜欢

转载自blog.csdn.net/u013946061/article/details/107864838
今日推荐