[物語を忘れました]°のブログへようこそ

Vue 学习笔记 [Part 7]

作者:故事我忘了
个人微信公众号:程序猿的月光宝盒

一. Promise

1.0 什么是Promise

ES6中一个非常重要和好用的特性就是Promise

​ 但是初次接触Promise会一脸懵逼,这TM是什么东西?

​ 看看官方或者一些文章对它的介绍和用法,也是一头雾水。

Promise到底是做什么的呢?

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

那什么时候我们会来处理异步事件呢?

​ 一种很常见的场景应该就是网络请求了。

​ 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。

​ 所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。

​ 如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。

但是,当网络请求非常复杂时,就会出现回调地狱。

​ OK,我以一个非常夸张的案例来说明。

我们来考虑下面的场景(有夸张的成分):

​ 我们需要通过一个url1从服务器加载一个数据data1data1中包含了下一个请求的url2

​ 我们需要通过data1取出url2,从服务器加载数据data2data2中包含了下一个请求的url3

​ 我们需要通过data2取出url3,从服务器加载数据data3data3中包含了下一个请求的url4

​ 发送网络请求url4,获取最终的数据``data4`

图片

上面的代码有什么问题吗?

​ 正常情况下,不会有什么问题,可以正常运行并且获取我们想要的结果。

​ 但是,这样额代码难看而且不容易维护。

​ 我们更加期望的是一种更加优雅的方式来进行这种异步操作。

如何做呢?就是使用Promise

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

1.1. Promise的基本使用

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

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

​ 假设下面的data是从网络上1秒后请求的数据

​ console.log就是我们的处理方式。

图片

这是我们过去的处理方式,我们将它换成Promise代码

图片

这个例子会让我们感觉脱裤放屁,多此一举

​ 首先,下面的Promise代码明显比上面的代码看起来还要复杂。

​ 其次,下面的Promise代码中包含的resolve、reject、then、catch都是些什么东西?

我们先不管第一个复杂度的问题,因为这样的一个屁大点的程序根本看不出来Promise真正的作用。

定时器异步事件解析

我们先来认认真真的读一读这个程序到底做了什么?

​ new Promise很明显是创建一个Promise对象

​ 小括号中((resolve, reject) => {})也很明显就是一个函数,而且我们这里用的是之前刚刚学习过的箭头函数。

​ 但是resolve, reject它们是什么呢?

​ 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)

​ resolve和reject它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。

成功还是失败?

​ 如果是成功的,那么通常我们会调用resolve(messsage),这个时候,我们后续的then会被回调。

​ 如果是失败的,那么通常我们会调用reject(error),这个时候,我们后续的catch会被回调。

OK,这就是Promise最基本的使用了。

Promise三种状态

首先, 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise

​ 异步操作之后会有三种状态

我们一起来看一下这三种状态:

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

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

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

图片

图片

1.2. Promise的链式调用

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

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

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

​ Promise.resovle():将数据包装成Promise对象,并且在内部回调resolve()函数

​ Promise.reject():将数据包装成Promise对象,并且在内部回调reject()函数

图片

简化版代码:

​ 如果我们希望数据直接包装成Promise.resolve,那么在then中可以直接返回数据

​ 注意下面的代码中,我把return Promise.resovle(data)改成了return data

结果依然是一样的

图片

1.3. Promise的all方法

有两个或以上的网络请求,要同时获得后处理,就用这个

图片

二. Vuex

2.0 干嘛的??

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

​ 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

​ Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

状态管理到底是什么?

状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。

​ 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。

​ 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。

​ 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?

等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?

​ 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。

​ 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。

​ 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。

2.1. 管理什么状态

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

​ 如果你做过大型开发,你一定遇到过多个状态,在多个界面间的共享问题。

​ 比如用户的登录状态、用户名称、头像、地理位置信息等等。

​ 比如商品的收藏、购物车中的物品等等。

​ 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的(待会儿我们就可以看到代码了,莫着急)。

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

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

我们先来看看单界面的状态管理吧.

单界面的状态管理

我们知道,要在单个组件中进行状态管理是一件非常简单的事情

​ 什么意思呢?我们来看下面的图片。

图片

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

​ State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)

​ View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)

​ Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。

写点代码,加深理解:

图片

图片

在这个案例中,我们有木有状态需要管理呢?没错,就是个数counter。

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

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

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

这不就是上面的流程图了吗?

多界面状态管理

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

​ 多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新)

​ 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)

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

​ 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。

​ 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!

​ 没错,Vuex就是为我们提供这个大管家的工具。

全局单例模式(大管家)

​ 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。

​ 之后,你们每个试图,按照我规定好的规定,进行访问和修改等操作。

​ 这就是Vuex背后的基本思想。

Vuex状态管理图例

图片

2.2. Vuex的基本使用

我们还是实现一下之前简单的案例

图片

首先,我们需要在某个地方存放我们的Vuex代码:

​ 这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件

​ 在index.js文件中写入如下代码:

图片

挂载到Vue实例中

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

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

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

图片

图片

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

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

​ 1.提取出一个公共的store对象,用于保存在多个组件中共享的状态

​ 2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到

​ 3.在其他组件中使用store对象中保存的状态即可

​ 通过this.$store.state.属性的方式来访问状态

​ 通过this.$store.commit('mutation中方法')来修改状态

注意事项:

​ 我们通过提交mutation的方式,而非直接改变store.state.count

​ 这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。

2.3. 核心概念

state -> 单一状态树

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

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

但是,它是什么呢?我们来看一个生活中的例子。

​ OK,我用一个生活中的例子做一个简单的类比。

​ 我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。

​ 这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。

​ 这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。

这个和我们在应用开发中比较类似:

​ 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。

​ 所以Vuex也使用了单一状态树来管理应用层级的全部状态。

​ 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

getters

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

​ 获取学生年龄大于20的个数。

图片

我们可以在Store中定义getters

图片

图片

Getters作为参数和传递参数

如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写

图片

getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.

​ 比如上面的案例中,我们希望根据ID获取用户的信息

图片

mutations

mutations状态更新

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

Mutation主要包括两部分:

​ 字符串的事件类型(type)

​ 一个回调函数(handler),该回调函数的第一个参数就是state。

mutation的定义方式:

图片

通过mutation更新

图片

Mutation传递参数

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

​ 参数被称为是mutation的载荷(Payload)

Mutation中的代码:

图片

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

​ 比如我们有很多参数需要传递.

​ 这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象.

​ 这个时候可以再从对象中取出相关的信息.

图片

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

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

图片

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

图片

Mutation响应规则

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

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

​ 提前在store中初始化好所需的属性.

​ 当给state中的对象添加新属性时, 使用下面的方式:

​ 方式一: 使用Vue.set(obj, 'newProp', 123)

​ 方式二: 用新对象给旧对象重新赋值

我们来看一个例子:

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

图片

如何才能让它改变呢?

​ 查看下面代码的方式一和方式二

​ 都可以让state中的属性是响应式的.

图片

Mutation常量类型

我们来考虑下面的问题:

​ 在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).

​ 当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.

​ 方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.

如何避免上述的问题呢?

​ 在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型.

​ 我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.

具体怎么做呢?

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

​ 定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.

图片

Mutation同步函数

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

​ 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.

​ 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.

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

图片

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

图片

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

So, 通常情况下, 不要再mutation中进行异步的操作

Actions 的基本定义

我们强调, 不要再Mutation中进行异步操作.

​ 但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢?

​ Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.

Action的基本使用代码如下:

图片

context是什么?

​ context是和store对象具有相同方法和属性的对象.

​ 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.

​ 但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说.

这样的代码是否多此一举呢?

​ 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?

​ 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.

Action

Action的分发

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

图片

同样的, 也是支持传递payload

图片

Action返回的Promise

前面我们学习ES6语法的时候说过, Promise经常用于异步操作.

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

OK, 我们来看下面的代码:

图片

Module

认识Module

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

​ Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.

​ 当应用变得非常复杂时,store对象就有可能变得相当臃肿.

​ 为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等

我们按照什么样的方式来组织模块呢?

​ 我们来看下边的代码

图片

Module局部状态

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

​ 我们在moduleA中添加state、mutations、getters

​ mutation和getters接收的第一个参数是局部状态对象

图片

注意:

​ 虽然, 我们的doubleCount和increment都是定义在对象内部的.

​ 但是在调用的时候, 依然是通过this.$store来直接调用的.

Actions的写法

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

​ 局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

图片

如果getters中也需要使用全局的状态, 可以接受更多的参数

图片

2.4. 目录组织方式

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

图片

三. 网络请求封装

3.1. 网络请求方式的选择

Vue中发送网络请求有非常多的方式, 那么, 在开发中, 如何选择呢?

选择一: 传统的Ajax是基于XMLHttpRequest(XHR)

​ 为什么不用它呢?

​ 非常好解释, 配置和调用方式等非常混乱.

​ 编码起来看起来就非常蛋疼.

​ 所以真实开发中很少直接使用, 而是使用jQuery-Ajax

选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax

相对于传统的Ajax非常好用.

为什么不选择它呢?

​ 首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.

​ 那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 你觉得合理吗?

​ jQuery的代码1w+行.

​ Vue的代码1w+行.

​ 完全没有必要为了用网络请求就引用这个重量级的框架.

选择三: 官方在Vue1.x的时候, 推出了Vue-resource.

​ Vue-resource的体积相对于jQuery小很多.

​ 另外Vue-resource是官方推出的.

为什么不选择它呢?

​ 在Vue2.0退出后, Vue作者就在GitHub的Issues中说明了去掉vue-resource, 并且以后也不会再更新.

​ 那么意味着以后vue-reource不再支持新的版本时, 也不会再继续更新和维护.

​ 对以后的项目开发和维护都存在很大的隐患.

选择四: 在说明不再继续更新和维护vue-resource的同时, 作者还推荐了一个框架: axios,为什么不用它呢?

​ axios有非常多的优点, 并且用起来也非常方便.

功能特点:

​ 在浏览器中发送 XMLHttpRequests 请求

​ 在 node.js 中发送 http请求

​ 支持 Promise API

​ 拦截请求和响应

​ 转换请求和响应数据

​ 等等

补充: axios名称的由来?

​ 没有具体的翻译.

​ axios: ajax i/o system.

3.2 axiox请求方式

支持多种请求方式:

paxios(config)

paxios.request(config)

paxios.get(url[, config])

paxios.delete(url[, config])

paxios.head(url[, config])

paxios.post(url[, data[, config]])

paxios.put(url[, data[, config]])

paxios.patch(url[, data[, config]])

如何发送请求呢?

​ 我们看下边的案例

发送get请求

图片

发送并发请求

有时候, 我们可能需求同时发送两个请求

​ 使用axios.all, 可以放入多个请求的数组.

​ axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2

图片

3.3. axios的相关配置

全局配置

在上面的示例中, 我们的BaseURL是固定的

​ 事实上, 在开发中可能很多参数都是固定的.

​ 这个时候我们可以进行一些抽取, 也可以利用axiox的全局配置

axios.defaults.baseURL = ‘123.207.32.32:8000’
axios.defaults.headers.post[‘Content-Type’] = ‘application/x-www-form-urlencoded’;

图片

常见的配置选项

请求地址

​ url: '/user',

请求类型

​ method: 'get',

请根路径

​ baseURL: 'http://www.mt.com/api',

请求前的数据处理

​ transformRequest:[function(data){}],

请求后的数据处理

​ transformResponse: [function(data){}],

自定义的请求头

​ headers:{'x-Requested-With':'XMLHttpRequest'},

URL查询对象

​ params:{ id: 12 },

查询对象序列化函数

​ paramsSerializer: function(params){ }

request body

​ data: { key: 'aa'},

超时设置s

​ timeout: 1000,

跨域是否带Token

​ withCredentials: false,

自定义请求处理

​ adapter: function(resolve, reject, config){},

身份验证信息

​ auth: { uname: '', pwd: '12'},

响应的数据格式 json / blob /document /arraybuffer / text / stream

​ responseType: 'json',

3.4. axios的创建实例

为什么要创建axios的实例呢?

​ 当我们从axios模块中导入对象时, 使用的实例是默认的实例.

​ 当给该实例设置一些默认配置时, 这些配置就被固定下来了.

​ 但是后续开发中, 某些配置可能会不太一样.

​ 比如某些请求需要使用特定的baseURL或者timeout或者content-Type等.

​ 这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.

图片

3.5. axios的封装

图片

3.6. axios的拦截器

axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理。

如何使用拦截器呢?

图片

拦截器中都做什么呢?

请求拦截可以做到的事情:

图片

请求拦截中错误拦截较少,通常都是配置相关的拦截

​ 可能的错误比如请求超时,可以将页面跳转到一个错误页面中。

响应拦截中完成的事情:

​ 响应的成功拦截中,主要是对数据进行过滤。

图片

响应的失败拦截中,可以根据status判断报错的错误码,跳转到不同的错误提示页面。

图片

至此,Vue知识点结束....所有笔记资料来源于

小码哥教育

教师:王红元

微博: coderwhy

微信: 372623326

感谢老师~

posted @ 2020-05-27 09:50  故事我忘了°  阅读(383)  评论(0编辑  收藏  举报