什么是 Promise 呢?

🎉Promise 到底是做什么的呢?

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

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

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

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

我们封装一个网络请求的函数,

因为不能立即拿到结果,所以不能像简单的 3+4=7 一样将结果返回。

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

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

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

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

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

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

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

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

如何做呢?就是使用 Promise。

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

🎉Promise 具体用法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Promise((resolve, reject) => {
setTimeout(() => {
//模拟异步请求
resolve("hello word");
reject("error");
}, 3000);
})
.then((data) => {
console.log("success");
})
.catch((error) => {
console.log("error");
});

//另外一种用法
new Promise((resolve, reject) => {}).then(
(data) => {}, //函数1满足时执行
(error) => {} //函数2不满足时执行
);

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

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()

🎉Promise 简写?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new Promise((resolve, reject) => {
setTimeout((data) => { //模拟异步请求
resolve('aaa')
reject('error')
}, 3000)
}).then((data) => {
console.log(data) //打印获取的初始结果
return data + '111' //拼接代码并传入下一个then
}).then((data) => {
console.log(data) //打印第一次拼接结果
return data + '222' //拼接代码并传入下一个then
}).then((data) => {
console.log(data) //打印第二次拼接结果
}).catch(
(error) => {
console.log('error')
}
)

Promise All?

1
2
3
4
5
6
7
8
9
Promise.all([
new Promise((resolve, reject) => {}, //第一个请求
new Promise((resolve, reject) => {} //第二个请求
]).then({
results => { //返回一个数组
result[0], //第一个请求结果
 result[1], //第二个请求结果
}
})

什么是 Vuex 呢?

🎉 初识 Vuex?

官方解释?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

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

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

状态管理到底是什么?

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

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

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

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

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

对象来管理吗?

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

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

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

🎉Vuex 管理什么东西?

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

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

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

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

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

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

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

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

🎉Vue 单界面的状态管理?

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

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

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

🎉Vue 多界面状态管理?

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

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

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

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

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

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

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

全局单例模式(大管家)

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

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

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

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from "vue";
import Vuex from "vuex"; //引入Vue
Vue.use(Vuex); //安装Vuex
export default new Vuex.Store({
//为了使用Vuex,创建对象
state: {
counter, //这里存放公共数据counter,通过$store.state.counter使用
},
mutations: {
//方法
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
},
actions: {},
modules: {},
});

main.js

1
2
3
4
5
6
7
8
9
10
11
//main.js中引入vuex
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
new Vue({
router,
store, //在vue组件内注册
render: (h) => h(App),
}).$mount("#app");

App.vue

1
2
3
4
5
6
7
8
9
10
11
export default {
name: "App",
methods: {
add() {
this.$store.commit("increment");
},
minus() {
this.$store.commit("decrement");
},
},
};

Vuex 核心概念?

🎉State?

Vuex 单一状态树?

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

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

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

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

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

实际应用开发?

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

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

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

🎉Getters?

基本使用方法?

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

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

我们可以在 Store 中定义 getters。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
state:{
students:[...]
}
getters:{
more20stu(state){//计算属性
return state.students.filter(s => s.age > 20) //$store.getters.more20stu
}
more20lenth(state,getters){//计算属性
return getters.more20stu.lenth
}
}

🎉Mutation?

基本使用方法?

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

Mutation 主要包括两部分:

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

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

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

App.vue(template 阶段,触发点击事件,并传入载荷)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
name: "App",
methods: {
//通过app的methods进行数据传递
add() {
this.$store.commit("increment");
},
minus() {
this.$store.commit("decrement");
},
increcount(count) {
this.$store.commit("incrementcount", count);
},
},
};

store.js(mutations 阶段,进行方法调用)

1
2
3
4
5
6
7
8
9
10
11
mutations:{//接收来自app传递的触发指令,执行数据更新
increment(state){
state.counter++
},
decrement(state){
state.counter--
},
incrementcount(state,count){
state.counter += count
}
}

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

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

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

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

提交风格差异?

1
2
3
4
5
6
7
//1.普通提交
this.$store.commit('提交需要执行的内容',count)
//2.特殊提交
this.$store.commit({
type:'提交需要执行的内容',
count //这时候提交的是对象,需要通过count.count获取对象中所需的值
})

Vue2.0 实现响应式添加和删除?

1
2
3
4
5
6
//数组
Vue.set(state.info, 1, "china");
Vue.delete(state.info, 1);
//对象
Vue.set(state.info, "address", "china");
Vue.delete(state.info, "address");

Mutation 常量类型 – 代码?

我们来考虑下面的问题:

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

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

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

如何避免上述的问题呢?

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

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

具体怎么做呢?

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

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

1
2
//mutation-types.js中定义一个常量
export const UPDATE = 'update-info'
1
2
3
4
5
6
//导入
import {UPDATE} from 'path'
//函数使用
[UPDATE](){...}
//提交使用
this.$store.commit(UPDATE,count)

🎉Action?

不要再 Mutation 中进行异步操作?

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

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

Action 的基本使用代码如下:

App.vue(template 阶段,触发点击事件)

1
2
3
4
5
6
7
8
9
//<button @click="appUpdateInfo()"></button>
export default {
name: "App",
methods: {
appUpdateInfo() {
this.$store.dispatch("aUpdateInfo");
},
},
};

store.js(action 阶段,进行异步请求,并传递)

1
2
3
4
5
6
7
8
actions:{
//context 上下文
aUpdateInfo(context){
setTimeout(() =>{
content.commit('updateInfo')
},3000)
}
}

store.js(mutations 阶段,进行方法调用)

1
2
3
4
mutations:{//接收来自action阶段传递的触发指令,执行数据更新
updateInfo(state){
state.counter++
}

}

context 是什么?

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

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

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

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

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

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

vuex 异步请求的回调?

方案一

1.App.vue(template 阶段,触发点击事件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//<button @click="appUpdateInfo()"></button>
export default {
name: "App",
methods: {
appUpdateInfo() {
this.$store.dispatch("aUpdateInfo", {
message: "我是传入的信息!",
success: () => {
console.log("异步操作完成!");
},
});
},
},
};

2.store.js(action 阶段,进行异步请求,并回调)

1
2
3
4
5
6
7
8
9
actions:{
//context 上下文
aUpdateInfo(context,payload){
setTimeout(() =>{
content.commit('updateInfo');
payload.success() //这里是回调操作
},3000)
}
}

方案二

1.App.vue(template 阶段,触发点击事件)

1
2
3
4
5
6
7
8
9
10
11
12
//<button @click="appUpdateInfo()"></button>
export default {
name: "App",
methods: {
appUpdateInfo() {
this.$store.dispatch("aUpdateInfo").then((res) => {
console.log("里面完成了提交!");
console.log(res);
});
},
},
};

2.store.js(action 阶段,进行异步请求,并回调)

1
2
3
4
5
6
7
8
9
10
11
actions:{
//context 上下文
aUpdateInfo(context,payload){
return new Promise((reslove,reject)=>{
setTimeout(() =>{
content.commit('updateInfo');
},3000);
reslove(111) //成功执行回调then,并传入参数
})
}
}

🎉Module?

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module:{
moduleA:{
state:{...},
getters:{...},
actions:{...},
getters:{...},
},
moduleB:{
state:{...},
getters:{...},
actions:{...},
getters:{...},
}
}

上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写.
我们在 moduleA 中添加 state、mutations、getters、mutations 和 getters 接收的第一个参数是局部状态对象。

🎉 项目结构?