【前端】Vuex笔记(超详细!!)

最近花了两周时间,完完全全的跟着Vuex官方的视频学完了Vuex并且详详细细的做了笔记,其中总结部分是我对于整个视频课程的总结,视频部分是跟着视频做的笔记,如果总结部分有不懂的话,直接去视频部分查找对应的笔记即可(笔记比总结更详细)。

本内容来自Vue官方推荐课程,随堂代码可以在连接中找到,当然还是建议跟着手敲一遍。

随着文章的越来越长,我的编辑器都卡顿起来了,编辑此博客时,更是觉得浏览器遇到了莫大的挑战,导出PDF总共48页,创作不易,求个关注!

文章目录

  • Vuex使用规范(总结)
    • 安装/配置 Vuex
        • 安装 Vuex
        • 配置 Vuex
    • Vuex 简介
        • Vuex 4种方法 + 1种分类方式
    • 调用Vuex中的内容
        • 综合 | 各种方法调用
        • 示例 1 | 调用 State
        • 示例 2 | 调用 Getter
        • 示例 3 | 动态Getter(让Getter可以接收参数)
        • 示例 4 | 调用 Mutations
        • 示例 5 | 调用 Actions
    • 使用 Module 分组
        • 示例 1 | 使用Module进行分组
          • 1. 调用同一个Module中的State
          • 2. 在Vue组件中调用state
          • 3. 在moduleB中调用ModuleA中的state
    • 使用 namespaced 对getters等三要素进行分组
        • Mutations
          • ModuleB中 调用 ModuleA
          • 在组件中调用
        • Actions
    • 组件化使用
    • VuexMapHelper 简介
  • 视频课程(笔记)
    • 1. 初始化项目 并 添加Mock数据
        • 初始化项目
        • 添加 Mock 数据
        • 创建商品列表组件
        • 代码解释
        • 补充知识 | Callback 回调函数
    • 2. 初步使用 Vuex
        • 安装Vuex
        • 创建 store/index.js
    • 3. 简单的使用
        • 1. 示例 1 | 设置值 与 获取值
    • 3. Vuex | Getter
    • 4. Vuex | Action
        • 示例 1 | Action 调用 state
        • 示例 2 | Action 调用 mutations
        • 示例 3 | 在项目中使用 Action
        • 补充 1 | 在Action中使用`{commit}`拆解对象
        • 示例 4 | 通过 new Promise来实现异步调用结果
    • 5. 全局注册store
    • 6. 商品添加至购物车
        • 1. 创建新的存储对象
    • 7. 通过 Vue DevTools 查看 Vuex 的内容
    • 8. 创建 ShoppingCard 组件
        • 1. 引入`currency.js`
        • 2. 创建`components/ShoppingCart.vue`组件
        • 3. 在`App.vue`中引入组件并使用
        • 4. 最终页面效果如下
    • 9. 结账功能
    • 10. 动态 getter | getter 接收参数
        • 需求实现 | 商品数量为0后不可继续下单
        • 需求优化 | 使用动态Getter
        • 解释说明
        • 动态Getter总结 | 让不可以接收参数的Getter方法变得可以接收参数
        • 补充 | 在其他地方使用这个可接受参数的Getter
    • 11. 使用 VuexMapHelper 减少代码
        • 使用`mapState`前后代码量的对比
        • 示例 1 | 简单使用 state
        • 示例 2 | 重命名state
        • 示例 3 | 使用方法传入state
        • 示例 4 | 示例3的改进-配合组件的变量返回数组中某个具体位置的值
        • 示例 5 | 同时使用多个 VuexMapHelper 的方式
          • 示例 5.1 关于使用多个 VuexMapHelper 的简单说明
          • 示例 5.2 | 探索 ES7 Object Spread Operator
          • 示例 5.3 对项目中代码进行改造
    • 12. 组件管理 1 |将各个组件单独存储
    • 13. 组件管理 2 | Vuex Modules 1
        • Modules 准备工作(代码)
        • Modules.State 注意事项 1 | 在组件中的使用
        • Modules.State 注意事项 2 | 在Store中使用
          • rootState | 在Getters中使用
          • rootState | 在Action中使用
    • 13. 组件管理 3 | Vuex Modules - NameSpaceSpace
        • 示例 1 | ModuleA 中调用 ModuleB 中的Getters示例
        • 示例 2 | 在组件中调用Module中的方法
        • 思考 | 在一个组件中调用两个Modules中的Getter方法
        • 示例 3 | 在组件中调用Actions方法
        • 示例 4 | 在ModuleA中调用ModuleB中的Actions与Mutations






Vuex使用规范(总结)

安装/配置 Vuex

安装 Vuex

使用下面的命令,可以在安装vuex

npm install vuex@version --save

如果不确定当前Vue应该使用的Vuex版本,可以执行下面命令让框架自己决定安装的版本。

npm install vue --save
配置 Vuex

创建store/index.html文件,内容如下:

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

Vue.use(Vuex)

export default new Vuex.Store({
    
    state: {

    },
    getters: {

    },
    mutations: {

    },
    actions: {

    }
})

main.js中全局定义store,代码如下:

import Vue from 'vue'
import App from './App'
import {currency} from "@/currency";

Vue.config.productionTip = false

Vue.filter('currency',currency)

// 导入store
import store from '@/store/index'

new Vue({
  el: '#app', 
  // 使用store
  store,
  render: h => h(App)
})



Vuex 简介

Vuex 4种方法 + 1种分类方式
  1. State 单一状态树,可以理解成是数据
  2. Getter 可以理解成是store的计算属性
  3. Mutation 用于更改数据(可以是set/update等)
  4. Action 类似于 mutation,但是它提交的是mutation,而不是直接变更状态,Action可以包含任意的异步操作
  5. Module 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决该问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

state/getters一般作为值来使用,一般放在computed内,使用计算属性作为“值”来用
mutations/actions可以传参,一般作为方法来使用,放在methods




调用Vuex中的内容


综合 | 各种方法调用
类型调用方式备注
Statestore.state.varName一般用在计算属性中作为一个“值”来使用
Getterstore.getter.methodName一般用在计算属性中作为一个“值”来使用
Mutationstore.commit('methodName',参数)一般作为方法来使用
Actionstore.dispatch('methodName',参数)一般作为方法来使用

示例 1 | 调用 State

在组件中调用

computed: mapState(['products'])
computed: mapState({
    allProduct: 'products'
})
computed: mapState({
    // 使用匿名方法 返回第一个值
    firstProduct1: state => state.products[0],
    // 使用具名方法 返回第一个值
    firstProduct2(state) {
        return state.products[0]
    }
})

示例 2 | 调用 Getter
computed: {
    mapGetter(['xxx'])
}
computed: mapGetter({
    allProduct: 'products'
})
computed: {
    total() {
      return store.getters.cartTotal
    },
}

示例 3 | 动态Getter(让Getter可以接收参数)

Getter 默认不能接收参数,但是我们可以通过让Getter返回一个匿名方法的方式,给Getter 传递一个参数。

Vuex中的Getter代码如下:

productIsInStock() {
    return (product) => {
        return product.inventory > 0
    }
}

在组件中调用并使用:

computed: {
    // 注意这是在computed中
    productIsInStock() {
        return store.getters.productIsInStock
    }
}

或者可以在方法中进行注册:

methods: {
    // 注意这里是methods中
    productIsInStock(product) {
      return store.getters.productIsInStock(product)
    }
}

随后调用productIsInStock(参数)即可


示例 4 | 调用 Mutations
store.commit('methodName',参数)

示例 5 | 调用 Actions
store.dispatch('methodName',参数)



使用 Module 分组

如果有非常多的不同业务的Vuex状态需要管理的话,将它们全部放在index.js中代码将会十分冗余,此时我们可以考虑将代码按照业务进行划分,将功能A的state/getters/mutations/getters放进ModuleA文件中,将B放入ModuleB中,以此来明确代码逻辑。

使用Module分组后,State会直接分组,此时要是调用state需要再前面加上Module的名称。


示例 1 | 使用Module进行分组

我们创建store/modules/moduleA.js文件,代码如下:

export default {

  state: {
    // 仅做示例
    items: []
  },

  getters: {
    // ...
  },

  mutations: {
    // ...
  },

  actions: {
    // ...
  }

}

随后,在store/index.js中引入该Module,代码如下:

import Vuex from 'vuex'
import Vue from 'vue'
// 引入
import moduleA from "./modules/moduleA";

Vue.use(Vuex)

Vue.config.devtools = true

export default new Vuex.Store({
  // 使用
  modules: {
    moduleA
  }
})

如此一来,我们就定义了一个名为moduleA的分组。

正如上文所说,此时的State将会被分组,原本代表根节点的state此时会变为代表此文件。此时调用State分为三种情况,如下:


1. 调用同一个Module中的State

调用同一个Module中的State(如ModuleA中的getters方法调用ModuleA的state),此时与之前无异,不会发生改变:

store.state.items

2. 在Vue组件中调用state

在Vue组件中调用state,此时需要再state前面加上Module的名称,以上面写的示例Module为例的话,代码如下:

store.moduleA.state.items

3. 在moduleB中调用ModuleA中的state

在moduleB中调用ModuleA中的state,此时需要使用到一个名为rootState的节点,且又分为三种情况,分别如下:

rootState简介:上面说到在没有进行分组的时候,我们可以使用state获取所有节点上的state(因为总共也就只有一个节点),但是分组后,state仅能代表当前文件下的所有state,因此Vuex又引入了rootState,代表根节点。

  1. 在Getter中调用

在Getter中使用其他Module中的state,首先我们需要传入至少三个参数,第三个参数为rootState,调用方式为rootState.moduleName.varName,以以上代码为示例的话,应该是rootState.moduleA.items

getters: {
    getterName(state,getter,rootState) {
        rootState.moduleA.items
    }
},
  1. 在Mutations中调用

无法获取

  1. 在Actions中调用

在Actions中调用其他Module中的state,我们应该传入四个参数,并且第四个参数为rootState。

调用方式与上面类似,如下:

actions: {
    actionMethodName({state, getters, commit, rootState}, varName){
        rootState.moduleA.items
    }
}



使用 namespaced 对getters等三要素进行分组

在上面的实例中,我们演示了使用module将不同业务的Vuex代码进行分组,但是上述代码中说明了以上代码只能对State分组,Getters/Mutations/Actions无法进行分组,此时在不同的module模块中,出现了两个名称相同的actions方法,那么我们在组件中调用的时候,这两个actions方法都将会被执行,这对于大型项目来说(多人协作开发)是非常危险的,因此我们可以使用namespaced对这三个部分也进行分组。

使用方式:在以上代码的基础上(使用Module进行分组的基础上),我们加入namespaced: true可以对此三种类型进行分组。

示例代码如下:

export default {
    
  // 启用getters/mutations/actions方法分组
  namespaced: true,
    
  state: {
    items: []
  },

  getters: {
    get1() {}
  },

  mutations: {
    // ...
  },

  actions: {
    // ...
  }

}

此时,State/Getter/Mutation/Action都被分组了。


Mutations

ModuleB中 调用 ModuleA

以在ModuleB的Actions中 调用MuduleA中的Mutations为例。

moduleB.js文件内容如下:

export default {
    actions: {
        actionMethodName({state, getters, commit}) {
            commit('moduleA/mutationName', variable, {root: true})
        }
    }
}

以上代码中:

  1. 通过moduleA/nutationName来指定调用moduleA中的方法
  2. commit表示是调用mutations
  3. {root: true}表示从根路径开始查找,作用等同于调用state时的rootState
    这三者组合起来,才能在一个module中调用另一个module中的mutations方法

moduleA.js中文件内容如下:

export default {
    mutations: {
        mutationName (state, variable) {
            // 对variable的操作
        }
    }
}

在组件中调用

分组之后,在组件中调用Module如下即可:

this.$store.commit("moduleA/methodName",参数);

或者,可以先使用mapMutations导入之后再当做方法进行调用。

以下代码为.vue组件中的代码:

export default {
    methods: {
        ...mapMutations('moduleA',{
            methed1: 'method1',
            method2: 'method2'
        }),
        ...mapMutations('moduleB',{
            methed3: 'method3'
        })
    }
}

或者如下这么写:

export default {
    methods: {
        ...mapMutations({
            methed1: 'moduleA/method1',
            method2: 'moduleA/method2',
            methed3: 'moduleB/method3'
        })
    }
}

Actions

Actions分组后,在组件中的调用方式如下:

methods: {
    ...mapActions('moduleA', ['methodName'])
}

methods: {
    ...mapActions(['moduleA/methodName'])
}

组件化使用

除了使用Modules按照功能对Vuex代码进行分组之外,我们还可以按照state/getters/mutations/actions进行分组(以actions为例)。

首先,创建store/actions.js文件,将store/index.js中actions的代码全部粘贴进去,如下:

actions.js文件:

export default { // = methods
    method1({commit}) {

    },

    method2({state,getters,commit},product) {

    },

    method3({state,commit}) {

    }

}

随后,我们在store/index.js中引入该文件,代码如下:

import Vuex from 'vuex'
import Vue from 'vue'
import actions from "./actions";


export default new Vuex.Store({

    state: {

    },

    getters: {

    },

    // 此处使用我们在actions.js中定义的actions方法
    actions,

    mutations: {

    }
})

以上代码即将actions方法单独引入到一个文件中,这样分模块划分代码可以让index.js中的代码更清晰,但是使用起来与不分组没有区别(不需要像module那样组名/方法名,一切还挂在“根”上)。

除了在actions.js文件中写actions方法,我们还可以再index.js中写actions方法,类似于特例与common的区别,使用起来没有任何不同。

要定义states,只需要创建states.js文件并export,随后在index.js中import并使用即可。

VuexMapHelper 简介

在上文中我们经常看到...mapState({})此类代码,这是为了让Vuex的引入更方便,减少我们的代码量,此种引用便是VuexMapHelper。

关于VuexMapHelper,下文有详细介绍,可以点此查看


视频课程(笔记)

🎥视频地址

1. 初始化项目 并 添加Mock数据

初始化项目
vue init vueschool/webpack-template shopping-cart
cd shopping-cart
yarn install
yarn dev
添加 Mock 数据

创建项目后,我们创建一个api/shop.js文件,代码可以如下 (也可以点此获取):

/**
 * Mocking client-server processing
 */
const _products = [
  { 'id': 1, 'title': 'iPad 4 Mini', 'price': 500.01, 'inventory': 2 },
  { 'id': 2, 'title': 'H&M T-Shirt White', 'price': 10.99, 'inventory': 10 },
  { 'id': 3, 'title': 'Charli XCX - Sucker CD', 'price': 19.99, 'inventory': 5 }
]

export default {
  getProducts (cb) {
    setTimeout(() => cb(_products), 100)
  },

  buyProducts (products, cb, errorCb) {
    setTimeout(() => {
      // simulate random checkout failure.
      (Math.random() > 0.5 || navigator.webdriver)
        ? cb()
        : errorCb()
    }, 100)
  }
}

创建商品列表组件

创建components/ProductList.vue文件,并添加以下代码:

<template>
  <div>
    <h1>Product List</h1>
    <ul>
      <li v-for="product in products">{{product.title}} - {{product.price}}</li>
    </ul>
  </div>
</template>


<script>
import shop from '@/api/shop'
export default {
  data() {
    return {
      products: []
    }
  },
  created() {
    shop.getProducts(products => {
      this.products = products;
    })
  }
}
</script>
代码解释

我们的mock数据中有一个getProducts (cb)方法,该方法的参数cd是一个函数,当我们调用该方法的时候,需要传递一个函数进去,该函数会调用一个定时函数,并在100ms之后将mock数据作为参数传递给该方法。

getProducts (cb) {
    setTimeout(() => cb(_products), 100)
},

我们在ProductList.vue中调用该方法,传递一个箭头函数进去,该箭头函数的参数是products,该方法传递到mock方法之后,如上所述,mock中的方法会将假数据作为参数放入该方法中,此时该箭头函数的参数就是我们创造的假数据,该箭头函数会将参数赋值给本地的参数,也就是this.products

shop.getProducts(products => {
    this.products = products;
})

这样一来,我们就通过传递函数的方式,将伪造的数据传递到了组件中去,这样的方式称为回调函数(Callback)

补充知识 | Callback 回调函数

阅读资料




2. 初步使用 Vuex

视频地址

安装Vuex

yarn add vuex

创建 store/index.js

我们将创建一个用于存放项目状态的store,代码如下:

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



Vue.use(Vuex)


new Vuex.Store({
  state: { // = data
    products: []
  },
  getters: { // = computed properties
    productsCount() {

    }
  },
  actions: {
    fetchProducts() {
      // make the call
    }
  },
  mutations: {
    setProducts() {
      // update product
    }
  }
})

Vuex 包含5种状态:

  1. State 单一状态树,可以理解成是数据
  2. Getter 可以理解成是store的计算属性
  3. Mutation 用于更改数据(可以是set/update等)
  4. Action 类似于 mutation,但是它提交的是mutation,而不是直接变更状态,Action可以包含任意的异步操作
  5. Module 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决该问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

我们可以在actions中写Ajax请求的方法(异步),在mutations中定义更新变量状态的方法。


3. 简单的使用

1. 示例 1 | 设置值 与 获取值

我们丰富一下示例代码,如下:

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: { // = data
    products: []
  },
  getters: { // = computed properties
    productsCount() {

    }
  },
  actions: {
    fetchProducts() {
      // make the call
    }
  },
  mutations: {
    setProducts(state,products) {
      // update product
      state.products = products
    }
  }
})

以上代码中,我们在store中创建了一个products,并且在mutations中创建了setProducts方法用以设置其值。


随后我们可以在Vue组件中使用它们,我们可以通过store.state.products的方式调用其值,也可以通过store.commit('setProducts',products)的方式修改其值,示例代码如下:

<script>
import shop from '@/api/shop'
import store from '@/store/index'

export default {
  computed: {
    products() {
      return store.state.products
    }
  },
  created() {
    shop.getProducts(products => {
      store.commit('setProducts',products)
    })
  }
}
</script>

以上代码中,我们在create中调用了shop.js中的回调函数,通过store.commit('mutations方法名',参数)的方式将mock数据存储到Vuex中去;

随后我们通过计算属性,将store.state.products(Vuex中products)的值重命名为products以方便使用。

在这里插入图片描述

页面正常显示



3. Vuex | Getter

视频地址

在之前的文章中我们使用store.state.products获取了所有的商品,现在我们稍微修改下代码,我们通过Vuex的getters,获取所有的库存大于0的商品,getters代码如下:

getters: { // = computed properties
    availableProducts(state,getters) {
        return state.products.filter(product => product.inventory > 0)
    }
}

随后,我们在components/ProductList.vue中使用getter,代码如下:

computed: {
    products() {
        // return store.state.js.products
        return store.getters.availableProducts
    }
}

以上代码中,我们定义了一个计算属性,并且返回Vuex中的getter方法,这样的话,我们就可以获取所有可用的商品。

当前商品的页面如下:

在这里插入图片描述

如果我将一个商品的库存修改为0(代码如下):

const _products = [
  { 'id': 1, 'title': 'iPad 4 Mini', 'price': 500.01, 'inventory': 0 },
  { 'id': 2, 'title': 'H&M T-Shirt White', 'price': 10.99, 'inventory': 10 },
  { 'id': 3, 'title': 'Charli XCX - Sucker CD', 'price': 19.99, 'inventory': 5 }
]

此时的商品页面将会减少一个商品:

在这里插入图片描述

成功!




4. Vuex | Action

视频地址

Action是Vuex的方法,我们可以使用异步方法等内容,我们可以在Action中通过context.commit调用mutations,或者使用context.state调用state中的变量。

示例 1 | Action 调用 state
context.state.products
示例 2 | Action 调用 mutations
// 会将旧的state替换为products
store.commit('setProducts',products)
示例 3 | 在项目中使用 Action

在之前的项目中,我们在ProductList.vue中,我们先在created中通过state.products = products给Vuex进行赋值,现在我们将这个方法抽离出来也放到Vuex中的Action中去,store/index.js中的代码如下:

  actions: {
    fetchProducts(context) {
      shop.getProducts(products => {
        context.commit('setProducts',products)
      })
    }
  },
  mutations: {
    setProducts(state,products) {
      // update product
      state.products = products
    }
  }

在这里插入图片描述

如此一来,我们可以仅仅通过store/index.js就可以初始化mock数据,随后我们只需要在ProductList.vue中调用该Action方法即可。

通过这种方式,我们将原本在ProductList.vue中进行的赋值操作(需要在ProductList.vue中import shop)移动到了store/index.js中(在store/index.js中import shop),代码更加简洁,ProductList.vue作为数据的调用方,仅仅调用数据即可,而不用关心数据的初始化操作。

computed: {
    products() {
      return store.getters.availableProducts
    }
  },
  created() {
    store.dispatch('fetchProducts')
  }
补充 1 | 在Action中使用{commit}拆解对象

我们还可以通过传入{commit}的方式,拆解对象,使用时不再需要context.commit,直接使用commit即可,代码如下所示:

actions: {
    fetchProducts({commit}) {
        shop.getProducts(products => {
            commit('setProducts', products)
        })
    }
}
示例 4 | 通过 new Promise来实现异步调用结果

我们将store/index.js中的代码做如下修改:

  actions: {
    fetchProducts({commit}) {
      return new Promise((resolve,reject) => {
        shop.getProducts(products => {
          commit('setProducts',products)
          resolve()
        })
      })
    }
  }

随后,在ProductList中,我们可以获取最终的执行结果:

  created() {
    console.log(this.loading)
    store.dispatch('fetchProducts')
      .then(() => console.log('执行结束了'))
  }



5. 全局注册store

视频地址

上面的方式我们每次使用store,都需要import store from 'xxx',这是很不方便的,我们可以直接在main.js中导入store,随后在需要使用的地方只需要this.$store就可以了,main.js中代码如下:

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

// 导入store
import store from '@/store/index'

new Vue({
    el: '#app',
    // 注册store
    store,
    render: h => h(App)
})

ProductList.vue中使用store

<script>
export default {
  data() {
    return {
      loading: false
    }
  },
  computed: {
    products() {
      // 使用 1
      return this.$store.getters.availableProducts
    }
  },
  created() {
    this.loading = true
    console.log(this.loading)
    // 使用 2
    this.$store.dispatch('fetchProducts')
      .then(() => this.loading = false)
  }
}
</script>



6. 商品添加至购物车

视频地址

1. 创建新的存储对象

本章节中,我们在store中创建一个新的存储对象cart,代码如下:

state: { // = data
    products: [],
    cart: []
},

随后,我们给该对象创建一个Action方法,其中调用三次Mutations方法,代码如下:

addProductToCart(context,product) {
  if(product.inventory > 0) {
    const cartItem = context.state.cart.find(item => item.id === product.id)
    if(!cartItem) {
      context.commit('pushProductToCart',product.id)
    } else {
      context.commit('incrementItemQuantity',cartItem)
    }
    context.commit('decrementProductInventory',product)
  }
}

三个Mutations方法代码如下:

pushProductToCart(state,productId){
  state.cart.push({
    id: productId,
    quantity: 1
  })
},
incrementItemQuantity(state,cartItem) {
  cartItem.quantity ++
},
decrementProductInventory(state,product) {
  product.inventory --
}

随后,我们在ProductList中调用Action方法,入参传入一个Product即可,代码如下:

<template>
  <div>
    <h1>Product List</h1>
    <img v-if="loading" src="../../../imgs/1.png" alt="Gif" style="width: 200px">
    <ul v-else>
      <li v-for="product in products">{{product.title}} - {{product.price}}
        <button @click="addProductToCart(product)">Add to Cart</button>
      </li>
    </ul>
  </div>
</template>


<script>
  import store from '@/store/index'
  export default {
    data() {
      return {
        loading: false
      }
    },
    computed: {
      products() {
        // return store.state.js.products
        return store.getters.availableProducts
      }
    },
    methods: {
      addProductToCart(product) {
        store.dispatch('addProductToCart',product)
      }
    },
    created() {
      this.loading = true
      console.log(this.loading)
      store.dispatch('fetchProducts')
          .then(() => this.loading = false)
    }
  }
</script>
  1. 以上代码中,我们在商品列表上点击加入购物车,此时会调用Action方法
  2. Action方法会判断当前商品的剩余数量是否大于0,否则不执行任何操作,是则继续向下执行
  3. 此时商品数量大于0,判断购物车中是否有该商品,如果有该商品则对购物车中该商品的数量+1,否则(购物车中没有该商品),向购物车中添加该商品的id与数量,数量是1
  4. 将商品的数量-1



7. 通过 Vue DevTools 查看 Vuex 的内容

8. 创建 ShoppingCard 组件

视频地址

接下来我们创建一个ShoppingCard组件,并将该组件添加到App.vue中,该组件主要显示我们已经添加到cart中的内容,以及当前cart中商品的总金额,所以我们还需要创建两个getter方法,来获取这些数据。

为了让金额显示的更像金额(前面带有$符号),我们需要引入一个小组件,点此可以下载(该步骤非必须)

1. 引入currency.js
  1. 在根目录下创建currency.js文件,将上方链接中的代码拷贝进去。
  2. 在main.js中添加以下代码:
import {currency} from "@/currency";
Vue.filter('currency',currency)
  1. 将页面中的金额做以下修改(添加| currency
{{product.price | currency}}
2. 创建components/ShoppingCart.vue组件

ShoppingCart.vue 代码如下:

<template>
  <div>
    <h1>Shopping Cart</h1>
    <ul>
      <li v-for="product in products">
        {{ product.title }} - {{ product.price | currency}} - {{product.quantity}}
      </li>
    </ul>
    <p>Total: {{total | currency}}</p>
  </div>
</template>


<script>
import store from "@/store/index";
export default {
  computed: {
    products() {
      return store.getters.cartProducts
    },
    total() {
      return store.getters.cartTotal
    }
  }
}
</script>

sotre/index.js中添加以下两个getter

// 用以获取商品列表
cartProducts(state) {
  return state.cart.map(cartItem => {
    const product = state.products.find(product => product.id === cartItem.id)
    return {
      title: product.title,
      price: product.price,
      quantity: cartItem.quantity
    }
  })
},
// 用以获取购物车总金额
cartTotal(state,getters) {
  return getters.cartProducts.reduce((total,product) => total + product.price * product.quantity,0)
}
3. 在App.vue中引入组件并使用
<template>
  <div id="app">
    <ProductList/>
    <hr>
    <ShoppingCart/>
  </div>
</template>

<script>
import ProductList from './components/ProductList'
import ShoppingCart from './components/ShoppingCart'

export default {
  name: 'app',
  components: {
    ProductList,
    ShoppingCart
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
4. 最终页面效果如下

在这里插入图片描述




9. 结账功能

视频地址

shop.js中有一个结账方法,代码如下:

  buyProducts (products, cb, errorCb) {
    setTimeout(() => {
      // simulate random checkout failure.
      (Math.random() > 0.5 || navigator.webdriver)
        ? cb()
        : errorCb()
    }, 100)
  }

为了模拟结账时成功或失败,这里做了一个随机数,如果随机数大于0.5则结账成功,否则失败,我们需要再Vuex中创建一个Action方法来模拟结账,代码如下:

checkout({state,commit}) {
  shop.buyProducts (
    state.cart,
    () => {
      commit('emptyCart')
      commit('setCheckoutStatus','success')
    },
    () => {
      commit('setCheckoutStatus','fail')
    }
  )
}

在这个Action方法中,我们调用了两个Mutations方法,其中emptyCart用于清空购物车,setCheckoutStatus用于当前的结账状态,我们在mutations中分别实现它们,代码如下:

setCheckoutStatus(state,status) {
  state.setCheckoutStatus = status
},
emptyCart(state) {
  state.cart = []
}

setCheckoutStatus中,我们还用到了一个名为setCheckoutStatus的新的state,我们在state中添加它,代码如下:

state: { // = data
    products: [],
    cart: [],
    setCheckoutStatus: null
},

随后,我们在components/ShoppingCart.vue中调用Action方法,就可以了,最终的使用代码如下:

<template>
  <div>
    <h1>Shopping Cart</h1>
    <ul>
      <li v-for="product in products">
        {{ product.title }} - {{ product.price | currency}} - {{product.quantity}}
      </li>
    </ul>
    <p>Total: {{total | currency}}</p>
    <!-- 在此添加按钮,调用我们自己定义的checkout方法 -->
    <button @click="checkout">Checkout</button>
    <!-- 这里显示结算成功/失败信息 -->
    <p>{{checkoutMessage}}</p>
  </div>
</template>


<script>
import store from "@/store/index";
export default {
  computed: {
    products() {
      return store.getters.cartProducts
    },
    total() {
      return store.getters.cartTotal
    },
    checkoutMessage() {
      return store.state.setCheckoutStatus
    }
  },
  // 在方法中调用store.Action的checkout方法,如果全局注册的话还可以直接@click="$store.dispatch('checkout')"
  methods: {
    checkout() {
      store.dispatch('checkout')
    }
  }
}
</script>



10. 动态 getter | getter 接收参数

视频地址

注意:Getter方法是不可以接收参数的(原本Getter只可以操作Vuex中的元素),这一章节的内容就是为了“让不能接收参数的Getter方法接收参数

需求实现 | 商品数量为0后不可继续下单

在以上代码中,我们的商品展示页面显示的都是剩余数量 > 0的商品,这有些不符合实际需求,我们想要显示所有的商品,并且在商品数量 <= 0的时候,商品的下单按钮变为Disabled。

这其实非常简单,我们只需要将获取商品的计算属性从getter替换到state,再在按钮中添加一个disabled状态就可以,代码如下:

<template>
  <div>
    <h1>Product List</h1>
    <img v-if="loading" src="../../../imgs/1.png" alt="Gif" style="width: 200px">
    <ul v-else>
      <li v-for="product in products">
        {{product.title}} - {{product.price | currency}} - {{product.inventory}}
        <!-- 原本button按钮没有disabled -->
        <button :disabled="!product.inventory > 0" @click="addProductToCart(product)">Add to Cart</button>
      </li>
    </ul>
  </div>
</template>


<script>
import store from '@/store/index'
export default {
  data() {
    return {
      loading: false
    }
  },
  computed: {
    products() {
      // 原本是 return store.getters.cartProducts
      return store.state.products
    }
  },
  methods: {
    addProductToCart(product) {
      store.dispatch('addProductToCart',product)
    }
  },
  created() {
    this.loading = true
    console.log(this.loading)
    store.dispatch('fetchProducts')
      .then(() => this.loading = false)
  }
}
</script>

现在页面已经可以符合我们的需求了,页面中的效果显示如下:

在这里插入图片描述

可以看到,当商品数量为0的时候,商品还在显示,只不过无法继续下单了。

需求优化 | 使用动态Getter

虽然我们已经实现了需求,但是方法却并不够“优雅”,我们想能否将:disabled="!product.inventory > 0"中的状态值也存储到Vuex中去,这样的话我们只需要调用Vuex的Getter方法,就可以获取当前商品的数量是否>0。

我们在普通的方法中去做这个功能的话,非常的简单,我们只需要在方法的参数中接收一个product,随后返回product.inventory > 0就可以了,但是Vuex的Getter方法与普通方法有一个很大的不同就是:Getter方法无法接受参数!!!,因此传入参数这种方式就无法使用了,那有没有什么方法可以实现呢?答案当然是有的:我们可以通过Getter返回一个方法,用该方法接收product参数,随后返回 product.inventory > 0 即可,接下来简单尝试下。

store/index.js中getter的方法:

productIsInStock() {
    return (product) => {
        return product.inventory > 0
    }
}

随后在需要使用的地方定义一个computed计算属性:

  computed: {
    // 注意这是在computed中
    productIsInStock() {
      return store.getters.productIsInStock
    }
  }

最后我们可以在button标签中使用这个计算属性:

<button :disabled="!productIsInStock(product)" @click="addProductToCart(product)">Add to Cart</button>
解释说明

看到这里也许你会有两个疑问

  1. 明明这个计算属性都没有接收参数,为什么使用的时候还需要传入一个参数呢?
  2. 将这个计算属性修改为方法可不可以呢?

问题1:我们在Vuex的getter方法中,实际是返回了一个方法,这个方法有一个参数product,返回的内容是productinventory是否大于0。这时候我们在计算属性中调用这个getter(一定注意这边是计算属性),因此getter中返回的返回的内容也就被我们重命名成为了计算属性的方法名,也就是(product) => return product.inventory > 0这个方法的名称被重命名成了productIsInStock这个计算属性,此时我们在:disabled="!productIsInStock(product)"中调用的时候,实际调用的就是getter中返回的函数,那么当然可以传入一个参数啦。

问题2:那么能不能不写在计算属性中,写在方法中呢?答案是可以(我本以为不可以,思考了下其实也行)因为我们计算属性的作用是重命名,可以直接将getter中返回的内容进行重命名,但是方法没有这个作用,如果要写在方法中的话,首先这个方法需要接收一个product参数,其次将这个参数传递进入getter方法,最后返回这个函数的值才可以。代码如下:

methods: {
    // 注意这里是methods中
    productIsInStock(product) {
      return store.getters.productIsInStock(product)
    }
}

经测试:可行

以上代码中,由于这个getter方法返回的是一个函数,所以这里的store.getters.productIsInStock直接可以看做是这个返回的函数,然后当然就可以对这个函数进行传参了。

动态Getter总结 | 让不可以接收参数的Getter方法变得可以接收参数

总结:动态Getter其实就是通过让Getter返回一个函数的方式,让原本不可以接收参数的Getter方法变得可以接收参数。

补充 | 在其他地方使用这个可接受参数的Getter

修改store中的Action方法,代码如下:

addProductToCart({state,getters,commit},product) {
  if(product.inventory > 0) {
    const cartItem = state.cart.find(item => item.id === product.id)
    if(!cartItem) {
      commit('pushProductToCart',product.id)
    } else {
      commit('incrementItemQuantity',cartItem)
    }
    commit('decrementProductInventory',product)
  }
}



11. 使用 VuexMapHelper 减少代码

视频地址

在之前的代码中,我们使用computed计算属性来处理Vuex中的值,我们每一个Vuex中的内容都需要三行代码才能在Vue组件中表达(Action用Methods,其它用Computed)。

如果使用MapHelper的话,首先要知道我们有mapStatemapGettermapAction…这意味着我们可以使用不同的Map来导入不同的内容(这一点很重要)。

使用mapState前后代码量的对比

视频地址

正如上面所说,我们可以使用mapState来替换掉冗余的代码,首先我们需要从vuex中引入mapState,随后可以使用,使用的方式有很多,下面来一一列举。

下面是不使用mapState/与使用mapState的对比

// 不使用mapState至少5行代码
computed: {
    products() {
      return store.state.products
    }
}
// 使用mapState只需要一行代码
computed: mapState(['products'])

可见,代码量确实大大降低了。

示例 1 | 简单使用 state

我们可以通过数组[]来导入state对象(代码如下),使用时直接调用state的名称即可。

请注意:这种情况下不支持重命名state,也就是说我们在Vuex.state中定义的内容是什么,在组件中就必须使用这个名称。

computed: mapState(['products'])
示例 2 | 重命名state

示例1中我们无法对state进行重命名,但是我们可以通过对象{}来解决这个问题。

computed: mapState({
    allProduct: 'products'
})

如此一来,我们就可以对state对象进行重命名,在这个组件中我们无法使用products来获取state中的值,只能使用allProducts来获取值。

示例 3 | 使用方法传入state

以下代码中,我们实现两种使用方法传入state的方式,他们的名称都为firstProduct$,并且传入这个数组的第一个值。

  computed: mapState({
    // 使用匿名方法 返回第一个值
    firstProduct1: state => state.products[0],
    // 使用具名方法 返回第一个值
    firstProduct2(state) {
        return state.products[0]
    }
})
示例 4 | 示例3的改进-配合组件的变量返回数组中某个具体位置的值

以下代码中,我们有一个indexOfProduct的组件变量,在获取组件值的mapState中,我们在funGetProduct$中数组的下标[x]指定为该值,此时我们可以获取这个state的第indexOfProduct个值,且随着此值发生变化,computed中的两个计算属性(funGetProduct$x2)返回的值也会发生变化。

<script>
  export default {
    data() {
      return {
        indexOfProduct: 0
      }
    },
    computed: mapState({
      // 使用匿名方法 获得具体位置的值
      funGetProduct1: state => state.products[this.indexOfProduct],
      // 使用具名方法 获得具体位置的值
      funGetProduct2(state) {
        return state.products[this.indexOfProduct]
      }
    })
  }
</script>
示例 5 | 同时使用多个 VuexMapHelper 的方式
示例 5.1 关于使用多个 VuexMapHelper 的简单说明

最开始的时候,我们代码量非常多的时候,我们向computed:{}中传入的都是一个{}对象,在上面四个示例中,我们探寻mapState的使用方式,传入的都是一个mapState对象,这两种传入方式的对比如下所示:

// 方式1 传统方式
computed: {
    products() {
        return store.state.products
    }
}
// 方式2 使用mapState方式
computed: mapState(['products'])

我们在Vuex中,除了state-mapState,还有getter-mapGetters、action-mapAction,如果仅仅采用上述中的方式2,是绝对达不到要求呢,那么能不能采用下述方式呢?

computed: {
    mapGetter(['xxx']),
        mapAction(['xxx'])
}

很明显,就从对齐方式上来说,似乎也是不可以的。且这几个方法返回的都是键值对,那么这样一来上述代码的computed就变成了{ {}, {} }了,这也不符合我们的需求,有没有方法可以解决这个问题呢?答案是:使用对象展开运算符(Object Spread Operator)

computed: {
    ...mapGetters(['xxx']), 
    ...mapAction(['xxx'])
}
示例 5.2 | 探索 ES7 Object Spread Operator

上文中我们提到了ES7的新特性:对象展开运算符(Object Spread Operator)。对象展开运算符...{}是指将{}中的对象展开为key:value,key:value的方式,下面我们来做简单的实验。

打开浏览器,点击F12,进入console,依次输入下面的代码:

const person = {firstName: 'Montgomery'}

const professor = {laseName: 'Montgomery',profession: 'Herpetologist'}

let result = {...person} 
// result的值为 {firstName: 'Montgomery'}

result = {...person,...professor} 
// result的值为 {firstName: 'Montgomery',laseName: 'Montgomery',profession: 'Herpetologist'}

result = {...person, ...professor, profession: 'Doctor'} 
// result的值为 {firstName: 'Montgomery',laseName: 'Montgomery',profession: 'Doctor'}

result = {...person,...professor,age: 20}
// result的值为 {firstName: 'Montgomery',laseName: 'Montgomery',profession: 'Herpetologist',age: 20}

可见,对象展开运算符有以下几个特点:

  1. 使用的时候需要将被展开对象放进{}中,这样展开之后还是一个对象
  2. 对象展开运算符可以拼接多个对象与键值对,如果存在重复内容,后面的内容会覆盖前面的内容
  3. 注意:可以将展开对象与键值对进行拼接
示例 5.3 对项目中代码进行改造
  1. 改造ProductList.vue代码
computed:{
    ...mapState({
        product: 'products',
    }),
    ...mapGetters({
        productIsInStock: 'productIsInStock'
    }),
},
// computed: {
//   products() {
//     // return store.state.js.products
//     return store.state.js.products
//   },
//   // 注意这是在computed中
//   productIsInStock() {
//     return store.getters.productIsInStock
//   }
// },
  1. 改造ShoppingCart.vue代码
computed: {
    ...mapState({
        checkoutMessage: 'setCheckoutStatus'
    }),
    ...mapGetters({
        products: 'cartProducts',
        total: 'cartTotal'
    }),
    // products() {
    //   return store.getters.cartProducts
    // },
    // total() {
    //   return store.getters.cartTotal
    // },
    // checkoutMessage() {
    //   return store.state.js.setCheckoutStatus
    // }
}



12. 组件管理 1 |将各个组件单独存储

视频地址

我们可以将actions/getters/mutations/state单独存放到一个文件中,随后在index.js中引入并使用它们,方便代码进行管理,下面我们创建一个store/actions.js文件,并在文件中粘贴以下代码:

store/actions.js

import shop from '@/api/shop.js'


export default {

  fetchProducts({commit}) {
    return new Promise((resolve,reject) => {
      shop.getProducts(products => {
        commit('setProducts',products)
        resolve()
      })
    })
  },

    addProductToCart({state,getters,commit},product) {
    if(product.inventory > 0) {
      const cartItem = state.cart.find(item => item.id === product.id)
      if(!cartItem) {
        commit('pushProductToCart',product.id)
      } else {
        commit('incrementItemQuantity',cartItem)
      }
      commit('decrementProductInventory',product)
    }
  },

    checkout({state,commit}) {
    shop.buyProducts (

      state.cart,

      () => {
        commit('emptyCart')
        commit('setCheckoutStatus','success')
      },

      () => {
        commit('setCheckoutStatus','fail')
      }
    )
  }
}

在store/index.js中引入并使用actions

import Vuex from 'vuex'
import Vue from 'vue'
// 引入actions
import actions from "./actions";

Vue.use(Vuex)

Vue.config.devtools = true

export default new Vuex.Store({
    // 使用actions
    actions,
    // ... 其余部分代码省略
})



13. 组件管理 2 | Vuex Modules 1

视频地址

在上一部分中,我们描述了将store中的四大模块分开存储,这样的话虽然降低了index.js中的代码量,但是对于整个新项目的逻辑条理并没有什么帮助。

我们可以看到,在该项目中主要是有一个商品(仓库)以及一个购物车组成的,那么我们能否将这两者进行区分,放到单独的js文件中,这样一来的话,我们可以对每一个模块进行区分,不仅可以简化index中的代码量,还可以让条理更加清晰。



Modules 准备工作(代码)

创建store/modules/cart.js文件,代码如下:

import shop from "@/api/shop";

export default {
    state: {
        cart: [],
        setCheckoutStatus: null

    },

    getters: {

        cartProducts(state) {
            return state.cart.map(cartItem => {
                const product = state.products.find(product => product.id === cartItem.id)
                return {
                    title: product.title,
                    price: product.price,
                    quantity: cartItem.quantity
                }
            })
        },

        cartTotal(state,getters) {
            return getters.cartProducts.reduce((total,product) => total + product.price * product.quantity,0)
        }

    },

    mutations: {
        // const cartItem = {id: 123,quantity: 2}
        pushProductToCart(state,productId){
            state.cart.push({
                id: productId,
                quantity: 1
            })
        },

        incrementItemQuantity(state,cartItem) {
            cartItem.quantity ++
        },

        setCheckoutStatus(state,status) {
            state.setCheckoutStatus = status
        },

        emptyCart(state) {
            state.cart = []
        }

    },

    actions: {
        addProductToCart({state,getters,commit},product) {
            if(product.inventory > 0) {
                const cartItem = state.cart.find(item => item.id === product.id)
                if(!cartItem) {
                    commit('pushProductToCart',product.id)
                } else {
                    commit('incrementItemQuantity',cartItem)
                }
                commit('decrementProductInventory',product)
            }
        },

        checkout({state,commit}) {
            shop.buyProducts (

                state.cart,

                () => {
                    commit('emptyCart')
                    commit('setCheckoutStatus','success')
                },

                () => {
                    commit('setCheckoutStatus','fail')
                }
            )
        }

    }

}

创建store/modules/cart.js文件,代码如下:

import shop from '@/api/shop.js'
export default {
  state: {
    products: []
  },

  getters: {
    availableProducts(state,getters) {
      return state.products.filter(product => product.inventory > 0)
    },

    productIsInStock() {
      return (product) => {
        return product.inventory > 0
      }
    }
  },

  mutations: {
    setProducts(state,products) {
      // update product
      state.products = products

    },

    decrementProductInventory(state,product) {
      product.inventory--
    }
  },

  actions: {
    fetchProducts({commit}) {
      return new Promise((resolve,reject) => {
        shop.getProducts(products => {
          commit('setProducts',products)
          resolve()
        })
      })
    }
  }
}

store/index.js中引入并使用modules,代码如下:

import Vuex from 'vuex'
import Vue from 'vue'
// 引入
import cart from "./modules/cart";
import products from "./modules/products";


Vue.use(Vuex)

Vue.config.devtools = true

export default new Vuex.Store({
  // 使用
  modules: {
    cart,
    products
  }
})

在以上代码中,我们将代码按照功能分为了两个不同的Modules,并且在index.js引入并注册,随后我们就可以使用我们定义的内容了。

此时进入浏览器的Vue插件中,Vuex存储内容如下图所示:

在这里插入图片描述

可以看到,所有的state都变成了文件名.state名称(state按照Modules名称进行分组了,但是Getters/Mutations/Actins不会分组)。因此,我们对State的使用方式也会发生一些变化,请看第二部分。



Modules.State 注意事项 1 | 在组件中的使用

我们将State按照其功能用Modules分组之后,State也会按照Modules名称进行分组,但是Getters/Mutations/Actions不会进行分组,仍然挂在全局的Namespace下。

因此,我们要是在组件中使用该state,我们需要通过state.modules.var的方式使用,代码如下所示:

ProductList.vue中computed部分的代码:

computed:{
    ...mapState({
        // 注意这一部分的代码,从原本的state.products,变成了state.products.products
        products: state => state.products.products
    }),
    ...mapGetters({
        productIsInStock: 'productIsInStock'
    }),
}

ShoppingCart.vue中computed部分的代码:

computed: {
    ...mapState({
        // checkoutMessage: 'setCheckoutStatus' 此种写法在Modules情况下不可用
        // 可以看到这一部分也百年成了state.modules.var的形式
        checkoutMessage: state => state.cart.checkoutMessage
    }),
    ...mapGetters({
        products: 'cartProducts',
        total: 'cartTotal'
    }),
},

只有这样,我们才能正常的调用到分组后的State中的内容。

Modules.State 注意事项 2 | 在Store中使用
我们无法通过ModuleA中的{state}参数调用ModuleB中的state值,但是{rootState}参数可以调用到所有的State值。

注意事项1中我们已经说明了,Store在划分Modules之后,State会分组,此时某个ModuleA中我们在Action/Getter中传入的{state}代表的仅仅是Local的State(也就是该文件内的),我们无法通过ModuleA中的{state}(参数)调用ModuleB中的state(值),我们可以引入一个新的变量{rootState}来表示根State,通过rootState可以调用到获所有的State。

rootState | 在Getters中使用

Getters 中第三个参数是rootState,这里我们没有使用到第二个参数getter,但是仍要保留在这里。

cartProducts(state,getter,rootState) {
    return state.cart.map(cartItem => {
        // 我们在这一行需要调用Mudule-products中的State,因此这里需要使用rootState
        const product = rootState.products.products.find(product => product.id === cartItem.id)
        return {
            title: product.title,
            price: product.price,
            quantity: cartItem.quantity
        }
    })
}
rootState | 在Action中使用

在Action中是第四个参数

addProductToCart({state,getters,commit,rootState},product) {
    if(product.inventory > 0) {
        const cartItem = state.cart.find(item => item.id === product.id)
        if(!cartItem) {
            commit('pushProductToCart',product.id)
        } else {
            commit('incrementItemQuantity',cartItem)
        }
        commit('decrementProductInventory',product)
    }
}



13. 组件管理 3 | Vuex Modules - NameSpaceSpace

视频地址

Vuex通过Module分组后有一个特点:如果ModuleA与ModuleB中存在一个同名的Action方法acfun()(请注意Action方法不分组),那么当我们在组件中调用acfun()方法的时候,两个Modules中的Action方法都会被执行

为了解决这一问题,我们可以使用NameSpace来区分不同组中的Action/Mutation/Getters。这样一来,可以避免多个开发者协同开发时的冲突问题。

我们在Module文件中写上namespace: true即对该Module启用了分组,示例代码如下:

import shop from '@/api/shop.js'
export default {
    
    // 此行代码开启Getter/Action/Mutation的分组功能
    namespaced: true,
    
    state: {
    
    },
    
    getters: {
    
    },
    
    mutations: {
    
    },
    
    actions: {
      
    }
  
}

添加namespaced: true之后,该Module内的Getters/Actions/Mutations都将会分组,因此将不会出现同名Action都被调用的问题。但是我们对分组后的此三种(Actions/Mutations/Getters)的调用方式也得修改一下。

需要注意的是,在分组后,ModuleA调用ModuleA(同组内)中的三种方法的方式还是不会发生改变,但是在ModuleA调用ModuleB中的方法,需要使用到rootGetters/rootActions/rootMutations,下面以Getters举例:

示例 1 | ModuleA 中调用 ModuleB 中的Getters示例

不加namespaced或者同Module中调用的代码如下:

actionMethod({state,getters},aVar) {
    getters.getterMethodName(aVar);
}

加上namespaced之后再ModuleA中调用ModuleB中的示例如下:

actionMethod({state,getters,rootGetters},aVar) {
    rootGetters['moduleB/getterMethodName'](aVar)
}

可以看到以上两个示例中,rootGetters是第三个参数,aVar是传入的参数,此时我们可以使用rootGetters调用根下的所有Getter。

示例 2 | 在组件中调用Module中的方法

如果不加namespaced,那么我们可以直接像下面这样调用:

computed: {
    ...mapGetters({
        products: 'cartProducts',
        total: 'cartTotal'
    })
}

加上namespaced之后,我们可以像下面这样调用:

computed: {
    ...mapGetters({
        products: 'cart/cartProducts',
        total: 'cart/cartTotal'
    })
}

以上代码中,cart是Module名称,cartProductscartTotal是两个Getter方法。

我们还可以像下面这样稍作简化,将cart提前:

computed: {
    ...mapGetters('cart',{
        products: 'cartProducts',
        total: 'cartTotal'
    })
}
思考 | 在一个组件中调用两个Modules中的Getter方法

如果不使用namespaced,那么我们的调用代码如下:

假设调用ModuleA中fun1与fun2方法/ModuleB中Fun3这三个Getter方法

computed: {
    ...mapGetters({
        fun1: 'fun1',
        fun2: 'fun2',
        fun3: 'fun3'
    })
}

加上namespaced之后:

computed: {
    ...mapGetters({
        fun1: 'ModuleA/fun1',
        fun2: 'ModuleA/fun2',
        fun3: 'ModuleB/fun3'
    })
}

简化之后:

computed: {
    ...mapGetters('ModuleA',{
        fun1: 'fun1',
        fun2: 'fun2'
    }),
    ...mapGetters('ModuleB',{
        fun3: 'fun3'
    })
}

这边比较有意思,简单思考下,我就不解释了。

示例 3 | 在组件中调用Actions方法

假设调用ModuleA中的fun1/fun2和ModuleB中的fun3

不使用namespaced:

methods: {
    ...mapActions({
        fun1: 'fun1',
        fun2: 'fun2',
        fun3: 'fun3'
    })
}

使用namespaced之后:

methods: {
    ...mapActions({
        fun1: 'moduleA/fun1',
        fun2: 'moduleA/fun2',
        fun3: 'moduleB/fun3'
    })
}

拆分为两个VuexMapHelper

methods: {
    ...mapActions('moduleA',{
        fun1: 'fun1',
        fun2: 'fun2'
    }),
    ...mapActions('moduleB',{
        fun3: 'fun3'
    })
}
示例 4 | 在ModuleA中调用ModuleB中的Actions与Mutations

看到这里必须得提个醒,State/Getters/Actions/Mutations的调用方式如下表:

类型调用方式备注
Statestore.state.varName一般用在计算属性中作为一个“值”来使用
Getterstore.getter.methodName一般用在计算属性中作为一个“值”来使用
Mutationstore.commit('methodName',参数)一般作为方法来使用
Actionstore.dispatch('methodName',参数)一般作为方法来使用

文章至此,我们已经描述了分组后的State如何调用,以及namespaced之后的Getter如何调用,这两种“值”都只是moduleName/getter即可。

但是,Mutation/Action不是通过store.mutation/action这样的方式调用的,这导致要在这两种“方法”中调用其它Module中的方法与Getter/State稍有区别,我们无法通过store.dispatch('moduleName/actionName')的方式来调用其它组件中的方法,而是要通过下面这种方式:

actions: {
    actionName(xxx) {
        // 调用其它Module中的Mutations方法
        commit('mutationMethodName',{root: true})
        // 调用其它Module中的Actions方法
        dispatch('actionMethodName',{root: true})
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/671522.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round 548 (Div. 2) C. Edgy Trees

Edgy Trees time limit per test: 2 second memory limit per test: 256 megabytes input: standard input output: standard output You are given a tree (a connected undirected graph without cycles) of n n n vertices. Each of the n − 1 n - 1 n−1 edges of the t…

计算机毕业设计 | SpringBoot招投标系统 任务发布网站(附源码)

1&#xff0c;绪论 在市场范围内&#xff0c;任务发布网站很受欢迎&#xff0c;有很多开发者以及其他领域的牛人&#xff0c;更倾向于选择工作时间、工作场景更自由的零工市场寻求零散单子来补贴家用。 如今市场上&#xff0c;任务发布网站鱼龙混杂&#xff0c;用户需要找一个…

【TCP协议中104解析】wireshark抓取流量包工具,群殴协议解析基础

Tcp ,104 ,wireshark工具进行解析 IEC104 是用于监控和诊断工业控制网络的一种标准&#xff0c;而 Wireshark则是一款常用的网络协议分析工具&#xff0c;可以用干解析TEC104 报文。本文将介绍如何使用 Wireshark解析 IEC104报文&#xff0c;以及解析过 程中的注意事项。 一、安…

STL-queue的使用及其模拟实现

在C标准库中&#xff0c;队列(queue)是一种容器适配器&#xff0c;它以先进先出的方式组织数据&#xff0c;其中从容器一端插入元素&#xff0c;另一端取出元素。 queue的使用 queue的构造函数 queue的成员函数 empty&#xff1a;检测队列是否为空size&#xff1a;返回队列中有…

7-14 字节序(Endianness)---PTA实验C++

一、题目描述 “内存寻址的最小单位是字节”——明白。 “每个字节有唯一的编号&#xff0c;称为地址”——明白。 “C中int通常为四个字节”——了解。 “int x 1;最低字节是1还是0&#xff1f;——纳尼&#xff1f; 事实上&#xff0c;这里有点小小分歧&#xff1a; 多字…

C++对C的增强

1、作用域运算符 ::解决归属问题&#xff08;谁是谁的谁&#xff09; 可以优先使用全局变量 2、命名空间 使用关键字namespace&#xff0c;控制标名称的作用域。 命名空间的本质&#xff1a;对符号常量、变量、函数、结构、枚举、类和对象等等进行封装 1、创建一个命名空间…

学习小记录——python函数的定义和调用

今日小好运&#xff0c;未来有好运。&#x1f381;&#x1f496;&#x1fad4; 分享个人学习的小小心意&#xff0c;一起来看看吧 函数的定义 函数通常来说就是带名字的代码块&#xff0c;用于完成具体的工作&#xff0c;需要使用的时候调用即可&#xff0c;这不仅提高代码的…

我的创作纪念日-砥砺前行

机缘 大家好&#xff0c;我是诊断协议那些事儿&#xff0c;又和大家见面了&#xff0c;记录一下创作日记&#xff0c;转眼间已经在CSDN平台创作三年了&#xff0c;最初仅仅是为了记录学习过程中的笔记&#xff0c;后来慢慢转为项目实践中的经验分享&#xff0c;当然更多的希望…

dm8 什么时候视图中统计的内存会超过OS

v$bufferpool和v$mem_pool视图记录着DMSERVER各组件的内存占用量。理论上跟OS看到的保持一致。但实际大多数场景下&#xff0c;OS中看到的数据远大于视图中的统计。这里面可能有内存泄漏的原因。不过也有的时候视图中的统计数据超过OS。下面就是这种情况&#xff1a; 上图中红线…

nas连接萤石云摄像机CTQ6X

需要准备的nassurveillance 请参考这个大佬的流程 https://www.bilibili.com/video/BV1ri4y1g7EN/ 踩坑&#xff1a; 一直到添加录像机验证一直没问题&#xff0c;但是验证一直不通过&#xff0c;后面下载了萤石云工作室的win桌面客户端&#xff0c;不知道是不是设置了预览还…

【Ubuntu】【Shell】执行sh脚本报错“xxx.sh:/bin/bash^M:解释器错误: 没有那个文件或目录“

背景 在自己Ubuntu环境执行sh脚本&#xff0c;报错"xxx.sh&#xff1a;/bin/bash^M&#xff1a;解释器错误: 没有那个文件或目录"&#xff0c;查了下是Ubuntu系统默认的shell是dash: 修改配置过下&#xff0c;变成bash 解决方案 在终端执行&#xff1a; sudo dp…

云队友:专业的远程工作和程序员接单平台,用户体验佳

编程赚钱的平台有不少&#xff0c;良莠不齐&#xff0c;今天给大家分享个专业的远程工作平台&#xff0c;以技术类工作为主&#xff08;包括编程&#xff09;&#xff1a; 云队友简介 外包大师是PMCAFF互联网产品社区于2016年推出的互联网产品技术外包服务平台。外包大师最新…

MyBatis 核心配置文件详细内容详解

1. MyBatis 核心配置文件详细内容详解 文章目录 1. MyBatis 核心配置文件详细内容详解2. 测试和学习的准备工作3. environment 标签4. transactionManager 标签5. dataSource 标签6. properties 标签7. mapper 标签8. 总结&#xff1a;9. 最后&#xff1a; 关于 MyBatis 这个核…

攻防世界---misc---2017_Dating_in_Singapore

1、题目描述 2、下载附件是一个pdf&#xff0c;里面是一个日历 3、题目描述是一些数字&#xff0c;直觉猜测是和日历的日期有关&#xff0c;仔细看题目的描述&#xff0c;会发现有个-连接&#xff0c;拆开之后发现一共有12组数据&#xff0c;再连联系到十二个月份&#xff0c;再…

音视频开发—FFmpeg播放YUV文件,YUV转换为JPEG操作

文章目录 1.使用命令行播放YUV数据1.1命令解析1.2参数说明 2.使用C语言实现将YUV数据转为JPEG图片格式2.1需求分析2.2读取YUV源文件2.3将YUV数据封装为AVFrame2.4将NV12 转换为YUV420平面格式2.5初始化MJPEG编码器2.6将YUV420P编码为JPEG2.7将编码数据写入图片文件2.8完整代码 …

从零开始实现自己的串口调试助手(4) -实现自动发送 / 时间显示

实现自动发送:checkBox 添加bool槽函数 bool 值&#xff0c;当√的时候为true 取消√ 位false 实现带bool 类型的槽函数: void Widget::on_checkBox_SendInTime_clicked(bool checked) {qDebug()<<"checkStatus:"<<checked;if(checked){ // 被勾选了//…

Python语言进阶学习

目录 一、类、对象和成员方法 二、构造方法 三、面向对象 &#xff08;1&#xff09;封装 &#xff08;2&#xff09;继承 单继承 多继承 复写 super&#xff1a;调用父类同名成员 &#xff08;3&#xff09;多态 &#xff08;4&#xff09;抽象类 五、Python操作…

Leecode---技巧---只出现一次的数字 / 多数元素

题解&#xff1a; 利用异或运算 a⊕a 0 的性质&#xff0c;可用来消除所有出现了两次的元素&#xff0c;最后剩余的即为所得。 class Solution { public:int singleNumber(vector<int>& nums){// 初始化为0int ans 0;for(int x: nums){// 异或操作ans ^ x;}retur…

Java循环结构while

1.while 是最基本的循环&#xff0c;它的结构为 while&#xff08;布尔表达式&#xff09;{ //循环内容 } 2.只要布尔表达式为true&#xff0c;循环就会一直执行下去 3.我们大多数情况是会让循环停止下来的&#xff0c;我们需要一个让表达式时效的方式来结束…