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

news2024/12/25 4:01:45

最近花了两周时间,完完全全的跟着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})
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp的tooltip功能放到表单laber

在uniapp中&#xff0c;tooltip功能通常是通过view组件的hover-class属性来实现的&#xff0c;而不是直接放在form的label上。hover-class属性可以定义当元素处于hover状态时的样式类&#xff0c;通过这个属性&#xff0c;可以实现一个类似tooltip的效果。 以下是一个简单的例…

9.Halcon3D点云力矩求解-平面拟合用法

1.实现效果 我们在使用3d相机对产品进行扫描生成点云的时候,由于安装问题,所以我们不可能保证每次产品扫描出来都在坐标系中位置和姿态非常标准。 上述算法描述的就是在某一个维度或者某几个维度上将点云数据和坐标系对齐; 至于怎么对齐,如何实现就是今天的内容。 本人能…

热电子光探测器的电磁场空间分布与FDTD材料折射率的导出

仿真实例 金属薄膜中金纳米孔阵列透射与反射&#xff0c; 并考虑其近场电磁分布 利用脚本进行电磁场及其光学响应的可视化 设置EOT型超表面结构&#xff0c;以及Structure library的使用 结构的参数化扫描与结果可视化 利用脚本计算峰值增强因子 多层平面结构激发T…

如何打造智能客服机器人的“超级大脑”?

一、引言 在数字化浪潮的推动下,智能客服机器人已成为企业服务升级、提升客户满意度的关键工具。然而,要让智能客服机器人真正发挥其在客户服务中的优势,打造其“超级大脑”至关重要。本文将深入探讨如何进一步提升智能客服机器人的智能水平,并结合其在多业务场景中面临的挑…

去掉el-table表头右侧类名是gutter,width=17px的空白区域(包括表头样式及表格奇偶行样式和表格自动滚动)

代码如下&#xff1a; <el-table:data"tableData"ref"scroll_Table":header-cell-style"getRowClass":cell-style"styleBack"height"350px"style"width: 100%"><el-table-column prop"id" l…

算法思想总结:哈希表

一、哈希表剖析 1、哈希表底层&#xff1a;通过对C的学习&#xff0c;我们知道STL中哈希表底层是用的链地址法封装的开散列。 2、哈希表作用&#xff1a;存储数据的容器&#xff0c;插入、删除、搜索的时间复杂度都是O&#xff08;1&#xff09;&#xff0c;无序。 3、什么时…

【机器学习】探索未来科技的前沿:人工智能、机器学习与大模型

文章目录 引言一、人工智能&#xff1a;从概念到现实1.1 人工智能的定义1.2 人工智能的发展历史1.3 人工智能的分类1.4 人工智能的应用 二、机器学习&#xff1a;人工智能的核心技术2.1 机器学习的定义2.2 机器学习的分类2.3 机器学习的实现原理2.4 机器学习的应用2.5 机器学习…

解锁 GPT-4o 背后数据带来的情绪价值

GPT-4o 可以说已经是一个富有情感、通人性的智能语音助手&#xff0c;或者更准确地说&#xff0c;是一个越来越接近人类交互的 “新物种”。这个强大的模型同时具备文本、图片、视频和语音理解和合成方面的能力&#xff0c;甚至可以被视为 GPT-5 的一个未完成版。 01 富有情感的…

【博客20】缤果Matlab串口调试助手V1.0(中级篇)

超级好用的Matlab串口调试助手 开发工具: MATLAB 2024a中文版 (编程语言matlab) -> Matlab APP Designer 目录 前言 一、软件概要&#xff1a; 二、软件界面&#xff1a; 1.App演示 ​ ​---- ◇♣♡♠ ---- 2.其他扩展App展示 ​编辑 三、获取 >> 源码以及G…

Android 图表开发开源库 MPAndroidChart 使用总结

1. 引言 电视项目中需要一个折线图表示节电数据变化情况&#xff0c;类比 H5 来说&#xff0c;Android 中也应该有比较成熟的控件&#xff0c;经过调研后&#xff0c;发现 MPAndroidChart 功能比较强大&#xff0c;网上也有人说可能是目前 Android 开发最好用的一个三方库了&a…

ChatGPT Edu版本来啦:支持GPT-4o、自定义GPT、数据分析等

5月31日&#xff0c;OpenAI在官网宣布&#xff0c;推出ChatGPT Edu版本。 据悉&#xff0c;这是一个专门为大学校园提供的ChatGTP&#xff0c;支持GPT-4o、网络搜索、自定义GPT、数据分析、代码生成等功能&#xff0c;可以极大提升学生、老师的学习质量和教学效率。 目前&…

算法-扫描线

目录 什么是扫描线算法&#xff1f; 扫描线简单应用 更多的扫描线 什么是扫描线算法&#xff1f; 在计算几何中&#xff0c;扫描线算法&#xff08;scan line algorithm&#xff09;一般用来解决几何图形的面积交并&#xff0c;周长交并问题&#xff0c;扫描线算法的核心思想…

AC/DC电源模块:应用于工业自动化领域

BOSHIDA AC/DC电源模块&#xff1a;应用于工业自动化领域 AC/DC电源模块是一种用来将交流电转换为直流电的电源模块。它在工业自动化领域有着广泛的应用&#xff0c;可以为各种设备和系统提供稳定可靠的电力供应。 一&#xff0c;AC/DC电源模块在工业自动化领域中起到了稳定…

洞察全球商机:精细化策略引领海外营销平台对接

随着全球市场的不断融合和互联网技术的飞速发展&#xff0c;企业越来越意识到海外营销与客服系统对接的重要性。 NetFarmer&#xff0c;作为一家专注于服务企业数字化出海的公司&#xff0c;对于海外市场的洞察和对接策略有着独特的见解。今天运营坛将深入探讨海外营销平台对接…

上市医疗巨头构建330+项自动化场景,实在Agent驱动效率与效益双重飞跃

历经二十年的快速发展&#xff0c;中国医疗信息化已迈入一个崭新阶段&#xff0c;其特征是产业链的高度集成、跨部门协同作业以及信息化的深度渗透。这一阶段不仅要求医护人员聚焦于以患者为中心的高质量服务&#xff0c;还提出了新挑战&#xff0c;即如何高效处理信息化系统伴…

Linux —— MySQL操作(1)

一、用户与权限管理 1.1 创建与赋予权限 create user peter% identified by 123465 # 创建用户 peter&#xff0c;# %&#xff1a;允许所有用户登录这个用户访问数据库 刚创建的新用户是什么权限都没有&#xff0c;需要赋予权限 grant select on mysql.* to peter%; # 赋予…

LeetCode2542最大子序列的分数

题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;两者长度都是 n &#xff0c;再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 &#xff0c;i1 &#xff0c;…&#xff0c; ik - 1 &#xff0c;你的 …

微信小程序区分运行环境

wx.getAccountInfoSync() 是微信小程序的一个 API&#xff0c;它可以同步获取当前账号信息。返回对象中包含小程序 AppID、插件的 AppID、小程序/插件版本等信息。 返回的对象结构如下&#xff1a; 小程序运行环境&#xff0c;可选值有&#xff1a;develop&#xff08;开发版&…

java实现地形dem产汇流流场数据提取解析

一、基础概念 在GIS和气象学、海洋学、大气科学、水文学等领域&#xff0c;"提取流场"通常指的是从数据集中识别和分析流体&#xff08;如水流、风场、洋流、大气流&#xff09;的运动模式和流向的过程。这个过程涉及数据处理、可视化和分析技术&#xff0c;下面是提…

【计算机网络】——概述(图文并茂)

概述 一.信息时代的计算机网络二.互联网概述1.网络&#xff0c;互连网&#xff0c;互联网&#xff08;因特网&#xff09;1.网络2.互连网3.互联网&#xff08;因特网&#xff09; 2.互联网简介1.互联网发展的三个阶段2.互联网服务提供者&#xff08;ISP&#xff09;3.互联网的组…