十六、状态管理——Vuex(3)

news2024/11/29 5:25:24

本章概要

  • action
    • 分发 action
    • 在组件中分发 action
    • 组合 action

16.7 action

在定义mutation 时,一条重要的原则就是 mutation 必须是同步函数。换句话说,在 mutation() 处理器函数中,不能存在异步调用。例如:

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

在 someMutation() 函数中调用 api.callAsyncMethod() 方法,传入了一个回调函数,这是一个异步调用。记住,不要这么做,因为这会让调试变得非常困难。
假设正在调试应用程序并查看 devtool 中的 mutation 日志,对于每个记录的 mutation,devtool 都需要捕捉到前一状态和后一状态的快照。然而在上面的例子中,mutation 中的 api.callAsyncMethod() 方法中的异步回调让这不可能完成。
因为当 mutation 被提交的时候,回调函数还没有被调用,devtool 也无法知道回调函数什么时候真正被调用。实质上,任何在回调函数中执行的状态的改变都是不可追踪的。

如果确实需要执行异步操作,那么应该使用 action。action 类似于 mutation,不同之处在于:

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

一个简单的 action 如下:

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

action 处理函数接收一个与 store 实例具有相同方法和属性的 context 对象,因此可以利用该对象调用 commit() 方法提交 mutation,或者通过 context.state 和 context.getter 访问 state 和 getter。甚至可以用 context.dispatch() 调用其他的 action。
要注意的是,context 对象并不是 store 实例本身。
如果在 action 中需要多次调用 commit,则可以考虑使用 ES6 中的结构语法简化代码。代码如下:

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

16.7.1 分发 action

action 通过 store.dispatch() 方法触发。如下:

store.dispatch('increment')

action 和 mutation 看似没有什么区别,实际上,他们之间最主要的区别就是 action 中可以包含异步操作。例如:

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

action 同样支持以载荷和对象方式进行分发。如下:

// 载荷是一个简单值
store.dispatch('incrementAsync',10)

// 载荷是一个对象
store.dispatch('incrementAsync',{
  amount:10
})

// 直接传递一个对象进行分发
store.dispatch({
  type:'incrementAsync',
  amount:10
})

一个实际的例子是购物车的结算操作,该操作涉及调用一个异步 API 和提交多个 mutation。代码如下:

actions:{
  checkout({commit,state},products){
    // 保存购物车中当前的商品项
    const savedCartItems = [...state.cart.added]
    // 发出结算请求,并乐观的清空购物车
    commit(type.CHECKOUT_REQUEST)
    // shop.buyProducts() 方法接收一个成功回调和一个失败的回调
    shop.buyProducts(
      products,
      // 处理成功
      () => commit(types.CHECKOUT_SUCCESS).
      // 处理失败
      () => commit(types.CHECKOUT_FAILURE,savedCartItems)
    )
  }
}

checkout 执行一个异步操作流,并通过提交这些操作记录 action 的副作用(状态更改)。

16.7.2 在组件中分发 action

在组件中可以使用 this.store.dispatch(‘XXX’) 方法分发 action ,或者使用 mapActions() 辅助函数将组件的方法映射为 store.dispatch 调用。如下:

store.js

const store = createStore({
  state(){
    return {
      count:0
    }
  },
  mutations:{
    increment(state){
      state.count++
    },
    incrementBy(state,n){
      state.count += n;
    }
  },
  actions:{
    increment ({commit}){
      commit('increment');
    },
    incrementBy({commit},n){
      commit('incrementBy',n)
    }
  }
})

组件

<template>
  <button @click="incrementBy(10)">
    You clicked me {{ count }} times.
  </button>
</template>
<script>
  import { mapActions } from 'vuex'
  export default{
    // ...
    methods:{
      ...mapActions([
        // 将this.increment() 映射为 this.$store.dispatch('increment')
        'increment',
        // mapActions 也支持载荷
        // 将 this.incrementBy(n) 映射为 this.$store.dispatch('incrementBy',n)
        'incrementBy'
      ]),
      ...mapActions({
        // 将 this.add() 映射为 this.$store.dispatch('increment')
        add:'increment'
      })
    }
  }
</script>

16.7.3 组合 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 才会执行。
下面给出一个简单的例子,来看看如何组合 action 来处理异步流程。代码没有采用单文件组件,而是在 HTML 页面中直接编写。如下:

ComposingActions.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
	</head>
	<body>
		<div id="app">
			<book></book>
		</div>
        <script src="https://unpkg.com/vue@next"></script>
        <script src="https://unpkg.com/vuex@next"></script>
        <script>
            const app = Vue.createApp({});
        	const store = Vuex.createStore({
        	  state() {
        	    return {
        	      book: {
        	        title: '《下沉年代》', 
        	        price: 168,
        	        quantity: 1
        	      },
        	      totalPrice: 0  
        	    }
        	  },
        	  mutations: {
        	    // 增加图书数量
        	    incrementQuantity (state, quantity) {
        	      state.book.quantity += quantity;
        	    },
        	    // 计算图书总价
        	    calculateTotalPrice(state){
        	      state.totalPrice = state.book.price * state.book.quantity;
        	    }
        	  },
        	  actions: {
        	    incrementQuantity({commit}, n){
        	      // 返回一个Promise
        	      return new Promise((resolve, reject) => {
        	        // 模拟异步操作
                    setTimeout(() => {
                      // 提交mutiation
                      commit('incrementQuantity', n)
                      resolve()
                    }, 1000)
                  })
        	    },
        	    updateBook({ dispatch, commit }, n){
        	      // 调用dispatch()方法触发incrementQuantity action,
        	      // incrementQuantity action返回一个Promise,
        	      // dispatch对其进行处理,仍旧返回Promise,
        	      // 因此可以继续调用then()方法
        	      return dispatch('incrementQuantity', n).then(() => {
        	        // 提交mutation
        	        commit('calculateTotalPrice');
        	      })
        	      
        	    }
        	  }
        	})
        	
        	app.component('book', {
        	  data(){
        		return {
        		  quantity: 1
        		}
        	  },
        	  computed: {
                ...Vuex.mapState([
                  'book',
                  'totalPrice'
                ])
              },
        	  methods: {
        		...Vuex.mapActions([
        		  'updateBook',
        		]),
        		addQuantity(){
        		  this.updateBook(this.quantity)
        		}
        	  },
              template: `
                <div>
                  <p>书名:{{ book.title }}</p>
                  <p>价格:{{ book.price }}</p>
                  <p>数量:{{ book.quantity }}</p>
                  <p>总价:{{ totalPrice }}</p>
                  <p>
                    <input type="text" v-model.number="quantity">
                    <button @click="addQuantity">增加数量</button>
                  </p>
                </div>`
          });

          app.use(store).mount('#app');
        </script>
	</body>
</html>

在 state 中定义了两个状态数据:book 对象和 totalPrice ,并为修改它们的状态分别定义了 mutation:incrementQuantity 和 calculateTotalPrice,之后定义了两个 action:incrementQuantity 和 updateBook,前者模拟异步操作提交 incrementQuantity mutation 修改图书数量;后者调用 dispatch() 方法触发前者的调用,在前者成功完成后,提交 calculateTotalPrice mutation,计算图书总价。
上述页面的展示效果如下:
在这里插入图片描述

当点击“增加数量”按钮时,在 addQuantity 事件处理函数中触发的是 updateBook action ,而在该 action 方法中调用 dispatch() 方法触发 incrementQuantity action,等待后者的异步操作成功后(1s后更新了图书的数量),接着在 then() 方法中成功完成函数调用,提交 calculateTotalPrice mutation ,计算图书总价,最终在页面中渲染出图书新的数量和总价。

本例只是用于演示如何组合 action 处理异步流程,并不具有使用价值,实际开发中对于本例完成的功能不要这么做。

下面继续完善购物车程序。首先为商品加入购物车增加一个数量文本字段,并绑定到 quantity 属性上。编辑 Cart.vue ,添加的代码如下:

Cart.vue

...
<tr>
    <td>商品价格</td>
    <td><input type="text" v-model.number="price" /></td>
</tr>
<tr>
    <td>数量</td>
    <td><input type="text" v-model.number="quantity" /></td>
</tr>
...

运行项目,添加数量字段后购物车页面的显示效果如下:
在这里插入图片描述

现在我们想实现当用户输入的商品编号与现有商品的编号相同时,在购物车中不新增商品,而只是对现有商品累加数量。只有当用户添加新的商品时,才加入购物车中。这个功能的实现,就可以考虑放到 action 中去完成。
编辑 store 目录下的 index.js 文件,在 actions 选项中定义一个加入商品到购物车的 action。如下:

store/index.js

...
getters:{
    cartItemPrice(state){
        return function (id){
            let item = state.items.find(item => item.id === id);
            if(item){
                return item.price * item.count;
            }
        }
    },
    cartTotalPrice(state){
        return state.items.reduce((total,item) => {
            return total + item.price * item.count;
        },0)
    }
},
actions:{
    addItemToCart(context,book){
        let item = context.state.items.find(item => item.id === book.id);
        // 如果添加的商品已经再购物车中存在,则只增加购物车中商品的数量
        if(item){
            context.commit('incrementItemCount',book);
        }
        // 如果添加的商品是新商品,则加入购物车中
        else{
            context.commit('pushItemToCart',book);
        }
    }
}
...

需要注意的是,在action 中不要直接修改状态,而应该通过提交 mutation 更改状态。
接下来编辑 Cart.vue ,通过分发 addItemToCart 这个 action 来实现商品加入购物车的完整功能。如下:

Cart.vue

import { mapMutations, mapState, mapGetters,mapActions } from 'vuex';
methods: {
    ...mapMutations({
        addItemToCart: 'pushItemToCart',
        increment: 'incrementItemCount'
    }),
    ...mapMutations([
        'deleteItem'
    ]),
    ...mapActions([
        'addItemToCart'
    ]),
    addCart() {
        // this.$store.commit('pushItemToCart', {
        this.addItemToCart({
            id: this.id,
            title: this.title,
            price: this.price,
            count: this.quantity
        })
        this.id = '';
        this.title = '';
        this.price = '';
    },
}

此时添加新商品和现有商品,测试一下购物车程序的运行情况。

提醒:
在本例中,简单起见,对是否是现有商品仅通过商品编号来判断,所以在添加商品时,只要编号相同就认为是同一种商品。而在实际项目中不会这么做。

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

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

相关文章

关于前端的学习

最近在网上想模拟一个ai的围棋&#xff0c;然后在gitee上找了一个算法&#xff0c;想要启动一下。https://gitee.com/changjiuxiong/myGoChess?_fromgitee_search使用说明是这样的&#xff1a;使用说明npm installnpm run dev打开index.html可自定义棋盘大小: new Game(19), n…

Polynomial Round 2022 (Div. 1 + Div. 2, Rated, Prizes!)(A~E)

A. Add Plus Minus Sign给出01字符串&#xff0c;在两两之间加入或者-&#xff0c;使得最后得到的结果绝对值最小。思路&#xff1a;统计1的个数&#xff0c;若是奇数个&#xff0c;那最后绝对值一定是1&#xff0c;否则为0&#xff0c;按照最后结果添加或1即可。AC Code&#…

GD32F103-初次接触

前期资料 外形 原理图 参考手册 1.芯片数据手册 2.用户手册 3.固件库使用指南 固件库解析 外设缩写 一些不常见的外设缩写。 BKP 备份寄存器 DBG 调式模块 ENET 以太网控制模块Ethernet EXMC 外部存储器控制 EXTI 外部中断事件控制器 FMC 闪存控制器 GPIO/AFIO 通用…

量化股票池数据怎么分析出来的?

量化股票池数据是怎么分析出来的呢&#xff1f;说到这个需要先来了解股票量化的基本原理&#xff0c;在正常的基础上&#xff0c;不是所有的股票数据都经过一一筛选&#xff0c;而是使用一些分析工具来执行&#xff0c;就像a股自动交易接口系统需要编写相符合条件的策略来执行&…

【MySQL】MySQL存储引擎,索引,锁以及调优

文章目录存储引擎MySQL中的索引MySQL 索引优缺点MySQL 索引类型MySQL索引的实现MySQL中的锁MySQL8.0 新特性MySQL中调优存储引擎 MySQL 5.7 支持的存储引擎有 InnoDB、MyISAM、Memory、Merge、Archive、CSV等等存储引擎。 通过show engines; 命令查看&#xff0c;如下图 图中…

【目标检测】Mask R-CNN论文解读

目录&#xff1a;Mask R-CNN论文解读一、Mask-RCNN流程二、Mask-RCNN结构2.1 ROI Pooling的问题2.2 ROI Align三、ROI处理架构四、损失函数一、Mask-RCNN流程 Mask R-CNN是一个实例分割&#xff08;Instance segmentation&#xff09;算法&#xff0c;通过增加不同的分支&…

PTA_1_基础编程题目集

文章目录PTA--基础编程题目集1、简单输出整数函数接口定义&#xff1a;裁判测试程序样例&#xff1a;输入样例&#xff1a;输出样例&#xff1a;题解&#xff1a;2、多项式求和函数接口定义&#xff1a;裁判测试程序样例&#xff1a;输入样例&#xff1a;输出样例&#xff1a;题…

车载以太网 - 路由激活处理机制 - 04

在前面我们已经介绍过DoIP的路由激活,不过主要是介绍路由激活的相关的概念;今天我们主要来介绍下路由激活的处理逻辑,进一步的了解软件对路由激活的处理机制,让我们更深入的了解DoIP这块的处理逻辑,更加有助于我们的工作中开发和测试工作的进行。 首先我们简单看下…

Python中的三目(元)运算符

Python中的三目(元)运算符 官方说明 https://docs.python.org/zh-cn/3/faq/programming.html#is-there-an-equivalent-of-c-s-ternary-operator 是否提供等价于 C 语言 "?:" 三目运算符的东西&#xff1f;有的。 语法形式如下&#xff1a; [on_true] if [expre…

细说——JWT攻击

目录介绍什么是JWTJWT有什么用为什么引入JWTJWT的组成JWT 特征识别JWT、JWS与JWE生成JWT视频介绍JWT攻击一些靶场JWT 攻击的影响是什么&#xff1f;JWT 攻击的漏洞是如何产生的&#xff1f;如何在 Burp Suite 中使用 JWT防御JWT攻击攻击工具爆破密钥工具&#xff1a;jwtcrack爆…

一个普通程序员,记录自己沪漂的2022年,2023年1月5日

或许对于每个人而言&#xff0c;2022年都是很艰难的&#xff0c;都是充满曲折的&#xff0c;仅仅以文字记录下我的2022年&#xff0c;我的沪漂生活。 今天是2023年1月5日&#xff0c;昨天的我做了一个梦&#xff0c;梦到自己捡到很多手机&#xff0c;于是做到工位的第一件事就…

(5)Qt中的日期和时间

QDate 日期对象格式化 d - 没有前导零的日子 (1 to 31) dd - 前导为0的日子 (01 to 31) ddd - 显示(缩写) 周一、周二、周三、周四、周五、周六、周日 dddd - 显示(完整) 星期一、星期二、…

微服务三个阶段

微服务三个阶段微服务三个阶段&#xff1a;微服务1.0&#xff1a;仅使用注册发现&#xff0c;基于Spring Cloud 或 Dubbo开发。微服务2.0&#xff1a;使用熔断、限流、降级等服务治理策略&#xff0c;并配备完整微服务工具和平台。微服务3.0&#xff1a;Service Mesh将服务治理…

https安全传输揭秘

HTTPS是什么 我们知道HTTP是明文传输的&#xff0c;恶意的中间人和窃听者通过截取用户发送的网络数据包可以拿到用户的敏感信息。尤其是涉及网上交易&#xff0c;银行转账等操作更是危害极大。 HTTPS的核心是SSL/TLS安全协议层&#xff0c;该层位于应用层和传输层之间&#x…

初识异常(Java系列10)

目录 前言&#xff1a; 1.异常的概念 2.异常的分类 2.1编译时异常 2.2运行时异常 3.异常的处理 3.1如何抛出异常 3.2异常的捕获 3.2.1异常的声明 3.2.2try-catch捕获并处理 3.3异常的处理流程 4.自定义异常类 结束语&#xff1a; 前言&#xff1a; 相信在大家学…

Windows常用命令整理

之前写了一篇关于Windows快速打开服务_陌客依天涯的博客-CSDN博客_服务快捷键 的文章&#xff0c;有表示windows还有很多常用的&#xff0c;那就整理一下&#xff0c;分享跟多点&#xff0c;希望对大家有用。 1、mstsc /*快速开启远程连接客户端*/ 2、regedit /*快速打开注册…

AcWing 1227.分巧克力(二分)

一、题目信息 二、思路分析 先从数据范围入手&#xff0c;这道题的数据范围最大是10的5次方&#xff0c;这就说明我们解决问题时的用到的算法的时间复杂度要控制在O(n)O(n)O(n)或者O(nlog(n))O(nlog(n))O(nlog(n))。 而O(nlog(n))O(nlog(n))O(nlog(n))的算法中最常用的是二分或…

基于RateLimiter+Aop+自定义注解实现QPS限流

QPS秒级限流一、Aop二、自定义注解三、测试类实例QPS简介&#xff1a;QPS&#xff08;Query Per Second&#xff09;&#xff0c;QPS 其实是衡量吞吐量&#xff08;Throughput&#xff09;的一个常用指标&#xff0c;就是说服务器在一秒的时间内处理了多少个请求 —— 我们通常…

控制流分析之构建支配树

控制流分析之构建支配树引言1 分析有向图2 构建支配树2.1 求最小半支配点2.2 求最近支配点引言 如上一个带有起始入口点的有向图为例&#xff0c;从A到Q的必经结点有A、L、M、Q&#xff0c;我们称其为Q的支配点&#xff0c;其中M为Q的最近支配点。我们将每个结点的最近支配结点…

分享124个PHP源码,总有一款适合您

PHP源码 分享124个PHP源码&#xff0c;总有一款适合您 源码下载 链接&#xff1a;https://pan.baidu.com/s/1AIktEQ0-cPRoSSAw_eA2Lw?pwdfb9m 提取码&#xff1a;fb9m 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0…