Vue-vuex -6

ES6补充 - Promise

Promise 用来做什么的

Promise到底是做什么的呢?

Promise是异步编程的一种解决方案。

那什么时候回来处理异步事件呢?

  • 一种很常见的场景就是网络请求
  • 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的 3+4=7 一样将结果返回
  • 所以往往我们会传入另一个函数,在数据请求成功时,将数据通过传入的函数回调出去
  • 如果只是一个简单的网络请求,那么这种方案不会带来很大的麻烦

但是,当网络请求非常复杂时,就会出现回调“地狱”(函数作为参数层层嵌套)

我们拿一个非常夸张的案例

网络请求的回调‘地狱’

我们看下下面的场景

在这里插入图片描述

  • 我们需要通过一个url1 从服务器加载一个数据 data1, data1中包含了下一个请求url2
  • 我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
  • 发送网络请求url4,获取最终的数据data4

上面的有什么问题吗?

  • 正常情况下,不会有什么问题,可以正常运行并且获取想要的结果。
  • 但是,这样代码难看而且不容易维护。
  • 我们更加期望的是一种更加优雅的方式来进行这种异步操作。

如何做呢?就是使用Promise。

Promise可以以一种非常优雅的方式来解决这个问题

Promise 的基本使用

我们先来看看 Promise 最基本的语法

这里,我们用一个定时器来模拟异步事件

  • 假设下面的data是从网络上1秒后请求的数据
  • console.log 就是我们的处理方式
    在这里插入图片描述
  • 下面是 Promise 代码
  <script>
        new Promise( (resolve, reject) => {
            setTimeout(function() {
                resolve('hello world')
                reject('error data')
            }, 1000)
        }).then(   (data) => {
            console.log(data);
        }).catch(  (error) => {
            console.log(error);
        })
    </script>

这个例子会让我们觉得 多此一举

  • 首先,下面的Promise代码中明显比上面的代码看起来还要复杂。
  • 其次,下面的Promise代码中包含的resolve、reject、then、catch都是什么意思?

定时器异步事件解析

我们来认真的读一下这个程序能干嘛?

  • new Promise很明显是创建一个Promise对象
  • 小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是箭头函数
  • 但是resolve, reject它们是什么呢?
  • 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
  • resolvereject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个
  • 成功还是失败?
  • 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调。
  • 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。

ok,到这里你已经了解了Promise 的基本使用

Promise 的三种状态

我们先来看下这 三种状态

  • pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。

  • fulfill:满足状态,当我们主动回调了resolve 时 ,就处于这种状态,并且回调 . then()

  • reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

在这里插入图片描述

在这里插入图片描述

Promise 的链式调用

我们在看Promise的流程图时,发现无论是then还是catch可以返回一个Promise对象

所以,我们的代码其实是可以进行链式调用的:

这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了

  • Promise.resovle(): 将数据包装成Promise对象,并且在内部回调resovle()函数
  • Promise.reject(): 将数据包装成Promise对象,并且在内部回调reject()函数
 <script>
        new Promise((resolve, reject) => {
            //  setTimeout 我们当做网络请求
            setTimeout(() => {
                resolve('hello world')
            }, 1000);
        }).then(data => { // 箭头函数 只有一个参数的时候 可以不写括号
            console.log(data); // hello world
            return Promise.resolve(data + '111')
        }).then(data => {
            console.log(data); //hello world111
            return Promise.resolve(data + '222')
        }).then(data => {
            console.log(data); //hello world111222
            return Promise.reject(data + 'error')
                // 这里回调了  reject
        }).then(data => {
            console.log(data); // 这里没有输出 ,这部分代码不会执行
            return Promise.resolve(data + '333')
        }).catch(data => {
            console.log(data); //hello world111222error
            return Promise.resolve(data + '444')
        }).then(data => {
            console.log(data); //hello world111222error444
        })
    </script>

链式调用简写

我们来简写 代码

  • 如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据
  • 注意下面的代码中,我把return Promise.resovle(data)改成了return data
    结果依然是一样的
 <script>
        new Promise((resolve, reject) => {
            //  setTimeout 我们当做网络请求
            setTimeout(() => {
                resolve('hello world')
            }, 1000);
        }).then(data => { // 箭头函数 只有一个参数的时候 可以不写括号
            console.log(data); // hello world
            return data + '111'
        }).then(data => {
            console.log(data); //hello world111
            return data + '222'
        }).then(data => {
            console.log(data); //hello world111222
            return Promise.reject(data + 'error')
                // 这里回调了  reject
        }).then(data => {
            console.log(data); // 这里没有输出 ,这部分代码不会执行
            return data + '333'
        }).catch(data => {
            console.log(data); //hello world111222error
            return data + '444'
        }).then(data => {
            console.log(data); //hello world111222error444
        })
    </script>

Promise 的all方法

使用场景 当一个需求 需要 请求两次才能执行下一步操作的话 就需要用到all方法

这里不过多介绍 大家了解就行
https://www.jianshu.com/p/7e60fc1be1b2

 <script>
        new Promise.all([
            new Promise((reslove, reject) => {
                setTimeout(() => {
                    reslove('result1')
                }, 2000)
            }),
            new Promise((reslove, reject) => {
                setTimeout(() => {
                    reslove('result2')
                }, 1000)
            })
        ]).then(results => {
            console.log(results); // 输出为一个数组
        })
    </script>

下面才是我们的主题

认识Vuex

什么是Vuex

官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

  • 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
  • Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

状态管理到底是什么

  • 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
  • 其实,你可以简单的将其看成把需要多个组件共享的变量(状态)全部存储在一个对象里面。
  • 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
    那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?

那为什么官方还要专门出一个插件 Vuex 呢?难道我们自己不能封装一个对象来管理呢?

  • 这当然可以,只是我们要先想想Vue.JS带给我们最大的便利是什么呢?没错,就是数据响应式
  • 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些
  • 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。

管理什么状态(变量)?

但是,有什么状态时需要我们在多个组件间共享的呢?

  • 但是,有什么状态时需要我们在多个组件间共享的呢?
  • 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
  • 比如用户的登录状态、用户名称、头像、地理位置信息等等。
  • 比如商品的收藏、购物车中的物品等等。
  • 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理
  • 而且它们还是响应式的。

从理论上理解了状态管理之后,让我们从实际的代码再来看看状态管理。

毕竟,Talk is cheap, Show me the code.(来自Linus)

先来看看单界面的状态管理

单界面的状态管理

在这里插入图片描述

这图片中的三个东西,怎么理解呢?

  • State : 状态,可以当做data中的属性
  • View : 视图层,可以针对State 的变化,显示不同的信息。
  • Actions : 用户的各种操作:点击,输入等,会导致状态的改变

单界面状态管理的实现、

首先我们来看下这个案例
在这里插入图片描述
在这里插入图片描述
在这个案例中,有一个状态需要我们管理 counter

counter 需要某种方式被记录下来,也就是我们的State

counter 目前的值需要被显示在界面中,也就是我们的View 部分

界面发生某些操作时(这里是点击事件),需要去更新状态,也就是我们的Actions

这样下来就是我们上面的流程图!

多界面状态管理

Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?

  • 多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
  • 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)

也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个视图共同想要维护的

  • 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
  • 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
  • 没错,Vuex就是为我们提供这个大管家的工具

全局单例模式(大管家

  • 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
  • 之后,你们每个视图,按照我规定好的规定,进行访问和修改等操作。
  • 这就是Vuex背后的基本思想。

Vuex 状态管理图例

来看下官方给的图
在这里插入图片描述

Vuex 基本使用

简单案例

我们还是实现一下前面的案例
在这里插入图片描述

官方文档 推荐一起看

首先我们先使用npm安装

npm install vuex --save

然后在src文件夹下创建一个文件夹 store,并且创建一个index.js文件

在这里插入图片描述

并且在刚刚创建的index.js 中写入下面的代码

import Vuex from 'vuex'
import Vue from 'vue'
// 安装 vuex
Vue.use(Vuex)

const store = new Vuex.store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++
        },
        decrement(state) {
            state.count--
        }
    }
})

export default store

挂载到Vue实例中

其次,让我们所有的组件都可以使用这个store对象

  • 来到main.js文件,导入store对象,并且放在new Vue

这样,在其他Vue组件中,我们就可以通过this.$store 的方式,获取到这个store对象

在这里插入图片描述

使用Vuex 的count

App.vue 写入以下代码

<template>
  <div id="app">
    <p>{{count}}</p>
    <button @click="inc">+1</button>
    <button @click="dec">-1</button>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {  
  },
  computed:{
    count: function() {
      return this.$store.state.count
    }
  },
  methods:{
    inc:function(){
      this.$store.commit('increment')
    },
    dec: function() {
      this.$store.commit('decrement')
    }
    
  }
}
</script>

<style>

</style>

ok,这就是使用Vuex 最简单的方式了

我们对使用步骤,做一个简单的小节:

  1. 提取出一个公共的store对象,用于保存在多个组件中共享的状态
  2. store对象放置在new Vue对象中,这样可以保证所有的组件都可以使用到
    3.在其他组件中使用store对象中保存的状态count变量)即可
  • 通过this.$store.state.属性的方法来访问状态
  • 通过this.$store.commit('mutations中的方法') 来修改状态(count的值)

注意事项:

  • 我们通过提交Mutation(见状态管理图)的方式,而非直接改变store.state.count
  • 这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值

Vuex 核心概念

Vuex 有几个比较核心的概念:

  • State
  • Getters
  • Mutation
  • Action
  • Module

State 单一状态树

Vuex提出使用单一状态树, 什么是单一状态树呢?

英文名称是Single Source of Truth(SSOT),也可以翻译成单一数据源

比如说

  • 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
  • 所以Vuex也使用了单一状态树来管理应用层级的全部状态。
  • 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
  • 在上面的案例中我们已经使用过 State,所以如何使用就不讲了

Getters 基本使用

有点像计算属性 computed

有时候,我们需要从store中获取一些state编译后的状态,比如下面的:

获取年龄大于20的个数
在这里插入图片描述

我们可以在store 中定义getters

在这里插入图片描述
让我们对比一下在computed中的写法
在这里插入图片描述

getters作为参数和传递参数

如果我们已经有了获取所有年龄大于20岁的学生列表(greaterAgesStus()),那么我们可以将代码写成这样:

在这里插入图片描述
这里我们通过greaterAgesStus()获取学生的列表,然后在greaterAgesCount()传入参数getters 在函数中通过 getters.greaterAgesStus 得到学生的个数

getters默认是不能传递参数的,如果希望传递参数,那么只能让getters本身返回另一个函数。
比如下面的案例中,我们希望根据ID获取用户的信息
在这里插入图片描述

Mutation

状态更新

Vuex的store状态更新唯一方式:提交Mutation

Mutation主要包括两部分:

  • 字符串的事件类型(type)
  • 一个回调函数(handler),该回调函数的第一个参数就是state。

定义方式

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

通过mutation更新 (写在 App.vue 中)

methons:{
	 increment :funtion(){
	 	this.$store.commit('increment')
	 }
}

传递参数

通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数

参数被称为是 mutation 的 载荷(payload) (下面2的位置)

//Mutation中的代码:
mutations: {
  increment (state, n) {
    state.count -= n
  }
}
//APP.vue 中的代码
methons:{
	 increment:function (){
	 	//2 也可以写成一个对象 并传入
	 	this.$store.commit('decrement',2)
	 }
}

但是如果参数不是一个呢?

  • 比如我们有很多参数需要传递
  • 这个时候,我们通常会以对象的形式传递,也就是 payload是一个对象
  • 这个时候可以再 从对象中取出相关的信息
//APP.vue 中传入对象  count
methons:{
	 increment:function (){	 	
	 	this.$store.commit('decrement',{count:2})
	 }
}
//Mutation中的代码:
mutations: {
  increment (state, payload) {
    state.count =  payload.count
  }
}

提交风格

上面的通过了commit 进行提交是一种普通的方式

Vue还提供了另一种风格,它是一个包含type属性的对象

//APP.vue 中传入对象  count
methons:{
	 increment:function (){	 	
	 	this.$store.commit({
			type:'increment', // 函数名称
			count:2
		})
	 }
}

Mutation中的处理方式是将整个commit的对象作为payload使用, 所以代码没有改变, 依然如下:

//Mutation中的代码:
mutations: {
  increment (state, payload) {
    state.count =  payload.count
  }
}

Mutation 需遵守 Vue 的响应规则

Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新

这就要求我们必须遵守一些Vuex对应的规则:

  • 提前在 store中初始化所需的属性
  • 当给state中的对象添加新属性时,使用下面的方式:

方式一: 使用Vue.set(obj,‘newProp’,123)
方式二:用新对象给旧对象重新赋值

下面来看下一个例子:

  • 当我们点击更新信息时,界面没有发生对应改变。

在这里插入图片描述

如何才能让它发生改变呢?

  • 查看下面代码的方式一和方式二
  • 都可以让state中的属性是响应式的

在这里插入图片描述

删除 -这个方法不是响应式的

上面是添加属性,那怎么删除属性呢

     //delete state.info.age  这个方法不是响应式的
     Vue.delete(state.info, 'age')
     //删除了 age 这个属性   

常量类型

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

我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.

 // mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

下面来看代码:

  1. 首先我们在store 文件下创建一个mutation-types.js,并写入以下代码

在这里插入图片描述 2. 然后我们在store 文件下的index.js 文件中导入上面的文件,并且将函数名进行替换

在这里插入图片描述
3. 在App.vue中同时引入 ,同时也是将进行替换

在这里插入图片描述

同步函数

通常情况下, Vuex要求我们Mutation中的方法必须是同步方法

  • 主要的原因是当我们当使用devtools(vue 插件 )时,可以帮助我们捕捉mutation的快照
  • 但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成

比如我们之前的代码,当执行更新时,devtools 会有下面的信息:

在这里插入图片描述

但是,如果Vuex 中的代码,我们使用了异步操作:

在这里插入图片描述

你会发现state中的info数据一直没有改变,因为它无法追踪到

所以,通常情况下,不要在mutation中进行异步操作

Action

定义

我们在上面强调,不要在mutation中进行异步操作

  • 但是某些情况,比如网络请求,必然是异步的,这个时候怎么处理呢?
  • Action 类似于Mutation ,但是用来替代Mutation进行异步操作。

Action 的基本使用代码如下:
在这里插入图片描述

context 是什么

  • context是和store对象具有相同方法和属性的对象.
  • 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
  • 注意,但它们并不是同一个对象

那这样的代码是否多次一举?

  • 我们定义了actions ,然后在actions 中去进行commit,这不是脱裤子放屁吗?
  • 事实上并不是这样,如果在Vuex中有异步操作,那么我们就可以在actives中完成

分发 Action 在组件调用

在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch

在这里插入图片描述

同样,也是支持传递payload
在这里插入图片描述

Action返回的Promise

前面学习ES6语法中说过,Promise 经常用于异步操作

  • Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应resolve或reject.

我们来看下面的代码

在这里插入图片描述

Module

认识

Module 是模块的的意思,为什么在Vuxe中我们要使用模块呢?

  • Vue使用单一状态树,那么意味着很多状态都会交给Vuex来管理
  • 当应用变得非常复杂时,store对象就有可能变得相当臃肿。
  • 为了解决这个问题,Vuex允许我们将store分割成模块(Modules),而每个模块都有自己的state ,mutations、actions、getters

我们按照什么样的方式来组织模块呢?
在这里插入图片描述

局部状态

上面的代码中,我们已经有了整体的组织结构,下面我们来看看具体的代码如何书写。

  • 我们在moduleA中添加state ,mutations、getters
  • mutation和getters接收的第一个参数是局部状态对象

在这里插入图片描述

  • 方便看
    在这里插入图片描述

注意

  • 虽然,我们的doubleCountincrement都是定义在对象内部的,
  • 但是调用的时候,依然是通过this.$store 来直接调用的。

Action的写法

actions的写法呢? 接收一个context参数对象

  • 局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
  • 这里使用了对象的解构

在这里插入图片描述

如果getters也需要使用全局的状态, 可以接受更多的参数
在这里插入图片描述

项目结构

我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰.

在这里插入图片描述

  • 展示

  • 引入 import 类名 from '路径'在这里插入图片描述

  • 导出 通过 export defaull{}
    -在这里插入图片描述

141

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值