目录
一、Promise概念
什么是Promise ? Promise是做什么的?
Promise是ES6中一个非常重要和好用的特性 ,简单来说,Promise是异步编程的一种解决方案。
什么时候我们需要处理异步操作呢?
一种很常见的场景应该就是网络请求: 我们封装一个网络请求的函数,是不能立即拿到数据的,所以不能跟简单的计算一样直接将结果返回。所以,我们往往会传入一个另外一个函数,在数据请求成功时,将数据通过这个函数回调出去。
如果只是一个简单的网络请求,那么这种方案,不会给我们带来很大的麻烦,因为一次简单的网络请求并不会耗费多少时间,用户也不会有一个很长的等待时间,代码的编写也不会有很大的问题。但是,实际上我们所要进行的并不是一个简单的网络请求,他有可能是一个很复杂的网络请求,此时,就有可能出现我们所说的回调地狱。而一旦出现回调地狱,这就意味着此时,我们需要进行一个很长时间的操作,而用户也就需要一个很长的等待时间,这个时候就需要用到异步操作了。
简单来说,当一个网络请求很复杂,用户在使用时需要等待时,我们就需要将其变为异步事件来处理。
什么是回调地狱?
例如,我们通过url1来加载数据data1,data1中包含了下一个请求的url2;因此,我们又需要 通过url2来加载数据data2,data2中包含了下一个请求的url3;因此,我们又需要 通过url3来加载数据data3,data3中包含了下一个请求的url4;因此,我们又需要 通过url4来加载最终的数据data4,在一层层返回数据,这种情况就被称之为回调地狱。
简单来说,就是当用户需要进行网络请求时,1需要调用2,2需要调用3,3需要调用4,直到4将数据请求成功再一层层回调,这显然是很浪费时间的一件事情。
不仅如此,此时的代码仅仅只有四层嵌套,当我们的代码量慢慢变大时,嵌套的循环就会越来越多,如果我们继续使用这样的方式,不仅代码不够美观,而且我们将来的维护工作也会变得很困难。
异步操作的原理
简单来说,就是,当用户需要进行网络请求时,为了避免用户等待网络请求结束而不能操作,体验不好。我们会将其变成异步操作。
也就是说,当用户需要进行网络请求,那就让一个函数来去请求,这个时候,我们的界面与用户仍在正常交互,等页面请求成功后,再将数据返回。
Promise是异步操作的一种解决办法,这种办法会让我们的异步操作的代码在较大时保持良好的可读性。
什么情况下使用Promise?
一般来说,当我们的项目中有异步操作时,我们需要用Promise将这个异步操作包裹起来,进行一个封装。
二、Promise的使用
当我们的开发中有异步操作时,就可以给异步操作包装一个Promise
2.1.promise的三种状态
pending:等待状态,比如,正在进行网络请求,或者定时器没有到时间
fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并回调.then()
reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并回调.catch()
2.2.promise的基本使用
2.2.1.Promise参数
传入的函数本身有两个参数:参数1:resolve;参数2:reject
resolve和reject本身就是两个函数。
resolve函数
在请求成功时调用 在调用resolve函数时,就是等于调用promise后面的then()方法
reject函数
在请求失败时调用 在调用reject函数时,就是等于调用promise后面的catch()方法
注意:
1.当我们没有请求失败的情况时,我们的参数reject可以省略。
2.一旦我们调用了resolve或是reject,执行then方法和catch方法,但是在其之后写的执行代码仍然会执行。
3.我们在使用promise函数时,我们的数据处理方式是写在then()和catch()中的,数据请求则是在上面写入。
new Promise((resolve, reject) => {
//请求需要的数据
resolve(data);//请求成功后调用该方法,data为then方法中的参数实参。
reject(error);//请求失败后调用该方法,error为catch方法中的参数实参。
}).then((data) => {
//请求成功后的代码逻辑(一般为数据处理)
}).catch((error) => {
//请求失败后的代码逻辑(一般为错误信息展示)
})
2.2.2.使用
简单请求
我们用一个定时器来代表我们的异步操作,执行代码为请求 hello world 数据并输出请求到的hello world。
setTimeout(() => {
let data = 'hello world';//请求数据
console.log(data);//输出数据
}, 1000)
我们使用我们的Promise来进行这个操作。
new Promise((resolve, reject) => {
setTimeout(() => {
let data = 'hello world';//请求数据
resolve(data);//请求成功后调用
reject('Error Date');//请求失败后调用
}, 1000)
}).then((data) => {
console.log(data);//请求成功后输出
}).catch((error) => {
console.log(error);//请求失败后输出
})
此时,我们发现相较于原来的setTimeout,代码量更多了,而且也并没有看出,Promise的作用。
复杂请求
我们可以试着来实现上面所说的回调地狱再看看,我们还是使用settimeout和我们的Promise来进行对比
settimeout
setTimeout(() => {
console.log('hello world');
setTimeout(() => {
console.log('hello vue.js')
setTimeout(() => {
console.log('hello python')
})
}, 1000)
}, 1000)
Promise
new Promise((resolve, reject) => {
//第一次网络请求的代码
setTimeout(() => {
resolve('hello world')
reject('Error Data')
}, 1000)
}).then((data) => {
//第一次拿到结果的处理代码
console.log(data);
return new Promise((resolve, reject) => {
//第二次网络请求的代码
setTimeout(() => {
resolve('hello vue.js')
}, 1000)
})
}).then((data) => {
//第二次拿到结果的处理代码
console.log(data);
return new Promise((resolve, reject) => {
//第三次网络请求的代码
setTimeout(() => {
resolve('hello python')
}, 1000)
})
}).then((data) => {
//第三次拿到结果的处理代码
console.log(data);
}).catch((error) => {
console.log(error);
})
这里,我们就可以明显看出,我们的Promise虽然代码量变大了,但是我们的代码的逻辑却很清晰,这对我们将来的维护是有很大作用的。
2.3.Promise另外处理方式
原本,我们的Promise请求失败时是通过调用catch方法来实现,我们也可以把catch省略,将其直接写入到then方法中,与我们的请求成功执行函数用,隔开。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello world')
reject('Error Data')
}, 1000)
}).then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
2.4.Promise的链式调用
2.4.1.链式调用一
就像我们2.2.2中的复杂请求,我们的promise在执行成功的时候,在then方法中再次调用我们的Promise,这就是链式调用。
2.4.2.链式调用二
我们还有另外一种链式调用的方法。
举例:
1.进行一次网络请求:请求数据aaa ->自己处理代码(10行)将aaa后面拼接上111
2.进行第二次处理:数据aaa111 ->自己处理代码(10行)在aaa111后面拼接上222
3. ......
普通方式一
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then((result) => {
//自己处理10行代码
console.log(result, '第一层的10行处理代码');
//对结果进行第一次处理
return new Promise(resolve => {
resolve(result + '111')
})
}).then((result) => {
console.log(result, '第二层的10行处理代码');
return new Promise(resolve => {
resolve(result + '222')
})
}).then((result) => {
console.log(result, '第三层的10行处理代码');
})
简化一
在上述的代码逻辑中,我们后面的第二次Promise以及第三次Promise都没有再进行网络请求等异步操作,仅仅是调用了Promise的resolve方法,此时,我们可以通过一种简写方式来让我们的代码变得更为精简。
new Promise(resolve => {resolve(结果)})简写Promise.resolve(结果)
这种简写方式,不仅适用于resolve,它也适用于我们的reject,也就是说,当我们的某一层请求失败的时候,我们也可以使用return Promise.reject(结果)。
return Promise.reject('error message')
具体使用
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then((result) => {
//自己处理10行代码
console.log(result, '第一层的10行处理代码');
//对结果进行第一次处理
//简写!!!!!!!!!
return Promise.resolve(result + '111')
}).then((result) => {
console.log(result, '第二层的10行处理代码');
//简写!!!!!!!!!
return Promise.resolve(result + '222')
}).then((result) => {
console.log(result, '第三层的10行处理代码');
})
简化二
我们除了上面那种简写方式以外,我们还可以通过直接 return 结果 来让我们的代码更加简单,当我们直接 return 结果 的时候,我们的内部逻辑会自己调用resolve
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then((result) => {
//自己处理10行代码
console.log(result, '第一层的10行处理代码');
//对结果进行第一次处理
//再次简写!!!!!!!
return result + '111'
}).then((result) => {
console.log(result, '第二层的10行处理代码');
//再次简写!!!!!!!
return result + '222'
}).then((result) => {
console.log(result, '第三层的10行处理代码');
})
2.5.Promise的reject
当请求失败是调用reject,需要注意的是,不管我们有几个reject,我们请求失败后调用的都是我们最后面所编写的那个catch。
简写方式中,简写方式一也可以使用reject,但简写方式二不行,这是因为内部调用的是resolve,并不是reject。
Promise.reject('error message')
2.5.1.请求失败的另一种方式throw
throw 抛出异常
我们请求失败不仅可以通过reject来告诉用户,我们也可以通过手动抛出异常的方式来通知。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then((result) => {
//自己处理10行代码
console.log(result, '第一层的10行处理代码');
//对结果进行第一次处理
//手动抛出异常!!!!!!
throw 'error message'
}).catch((error) => {
console.log(error);
})
2.6.Promise.all()
参数
数组
作用
它会将,数组中传递的方法或函数的运算结果保存到一个数组中。如果,有一个需求需要我们请求两个数据才能实现时,我们可以通过这个all()方法来直接对多个数据进行处理。
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result1')
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result2')
}, 1000)
})
]).then((result) => {
console.log(result);//这个result是一个数组,数组中保存的是我们上面请求到的result1和result2
})
三、Vuex概念
什么是Vuex?
Vuex是一个专门为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex也集成到Vue的官方调试工具devtools extension,提供了诸如零配置的。time-travel调试、状态快照导入导出等高级调试功能。
状态管理是什么?
状态管理模式、集中式存储管理这些名词只是看是很难理解的。简单理解一下,我们项目的组件之间是有一定的关联的,因此,很有可能我们有多个组件都需要使用到某一个变量。但是,一个组件是一个完整的封装,怎么才能使用同一个变量呢?这就是我们Vuex的作用了。
Vuex是把把需要多个组件共享的变量全部存储在一个对象里。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。那么,多个组件就可以共享这个对象中的所有变量属性了。
官方Vuex的作用
当然了,一个简单状态管理,我们自己也可以完成创建。但是,自己简单封装的对象是做不到响应式的,而我们的Vue的一个重要特性就是响应式开发;那么,我们可以封装实现一个响应式的对象嘛?可以,代码都是人写出来的,但是没有必要,毕竟,官方都给我们准备好了,我们可以直接使用Vuex。
Vuex管理什么状态?
在小型项目中,我们的用户信息有可能需要在多个页面进行展示,更别说大型项目,一定会有多个状态需要在多个界面件的共享。比如,用户的登录状态、用户名称、头像、地理位置信息等,比如商品的收藏、购物车中的信息等,这些状态信息,我们都可以使用Vuex对他进行保存和管理,而且他们还都是响应式。
3.1.Vuex的store文件夹目录结构
注意
这里的文件夹目录是我们最终要形成的一个文件夹目录。
index.js
actions.js
mutations.js
mutations-type.js
modules.js
3.2.单页面的状态管理
State
就是我们的状态(data中的数据对象)
View
视图层。针对State的变化,显示不同的信息
Actions
主要是用户的各种操作:点击、输入等,导致状态的改变
理解
首先,我们通过State也就是数据,将数据在View视图层展示给用户,而在展示的过程中,用户可能会进行一些Actions,比如点击等操作,这就会使得我们的State发生改变,进而再次影响到View 也就是说 ,State->View->Actions->State刚好组成了一个环。
注意
单页面并无共享状态,这是因为,只有一个页面,那么他的状态也就是数据 ,放到自己的页面中保存即可,没有必要使用Vuex多此一举。也就是说,它用不到我们的Vuex。
3.3.多页面状态管理
什么是多页面状态管理?
前面已经提到过了,状态管理就是将多个页面的State进行集中管理,那么多页面状态管理,实质上就是将两个或两个以上的页面所需要的状态放到一起统一管理。
3.3.1.多页面状态管理的条件
1.多个视图都依赖同一个状态(一个状态该了,多个界面需要进行更新)
2.不同界面的Actions都想修改同一个状态
理解:
也就是说,对于某些状态(状态1/状态2/...)来说,它只会被某一个组件使用和改变,也就是说只属于我们的某一个视图,但是也有一些状态(状态a/状态b/...)会被许多组件改变状态,是属于多个视图共同维护。
1.状态1/状态2/...你放到自己的房间中,自己管理自己用
2.状态a/状态b/...我们希望交给一个大管家来统一帮我们管理
3.Vuex就是为我们提供这个大管家的工具
3.3.2.全局单例模式(大管家)
1.我们现在要做的就是将共享的状态抽出来,交给我们的大管家Vuex,统一进行管理
2.之后,你们每个视图,按照我规定好的规定,进行相应的访问和修改操作
3.这就是Vuex背后的基本思想
3.2.3.Vuex的核心概念(五个)
1.State:保存共享状态的地方
单一状态树 英文:Single Source of Truth[单一数据源]
举例说明
在国内我们有很多信息需要被记录,比如上学时的个人档案、工作后的社保记录以及其他相关的户口、医疗、文凭等信息,这些信息被分散在很多地方进行管理
优点
1.这种方式结构较为清晰
2.这种方式架设某个系统被入侵,则只暴露出来我们的某一方面的信息,而不是全部。
缺点
当我们需要办理某个业务时(比如入户某个城市),我们需要去不同的地方打印不同的信息并盖不同的章,最后到某个地方证明信息无误。即便如此,假设我们某个信息出错,我们需要重新回到特定地方去重新办理。因此,该种方式低效、不方便管理、维护工作也需要大量人力来维护
总结
单一状态树指的就是:我们即使有很多信息需要管理,我们也仅仅使用一个store。
如果我们的状态信息时保存在多个Store对象中的,那么管理和维护会很困难。因此Vuex使用了单一状态数来管理应用层级的全部状态。单一状态树能够让我们最直接的方式来找到某一个状态的片段。而且在之后的维护和调试过程中,也非常方便管理和维护。
2.Getters:类似组件计算属性computed
3.Mutations:进行同步操作的地方
4.Actions:进行异步操作
5.Module:专门给他划分一些模块,针对不同模块在其中进行一些相关数据的保存
四、Vuex的使用
4.1.前期准备
1.我们在App组件中创建一个counter状态,可以通过点击按钮的方式让这个counter增加或减少,假定,我们还有一个组件叫HelloVuex,该组件也需要对状态counter进行一些操作,此时,counter就是我们的公共状态,使用Vuex
4.2.Vuex的基础使用
4.2.1.通过npm下载安装我们的Vuex组件
执行代码:npm install vuex --save
4.2.2.通过下载的vuex创建对象并将其挂载到实例中
·1.创建一个store文件夹,在文件夹中创建一个index.js
注意
类似vue-router一样,我们的vuex如果挂载到main.js中 ,则会使得main.js中的代码过多以及复杂
解决
因此,我们一般会创建一个文件夹store来对vuex中的代码统一管理
2.在index.js文件中import导入我们所需要的vue和vuex
import Vue from "vue";
import Vuex from 'vuex';
3.通过Vue.use()方法将Vuex安装
Vue.use(Vuex)
4.创建store对象对状态进行管理
const store = new Vuex.Store({
state: {//保存状态
},
mutations: {//同步操作(修改状态)
},
actions: {//异步操作(分发工作)
},
getters: {//类似计算属性
},
modules: {//模块
}
})
5.在创建好的store对象中通过state保存需要共享的状态,或是通过mutations保存对状态进行的操作等
state: {
counter: 1000
}
6.将创建好的store对象导出 export default store
export default store
7.在main.js中引入该对象,并像router一样注册
import store from "./store";//导入
new Vue({
el: '#app',
router,
store,//注册
render: h => h(App)
})
8.此时,我们就可以在所有组件中通过$store.state.counter来查看状态。
<h2>{{ $store.state.counter }}</h2>
4.3.mutations:(同步操作)
4.3.1.为什么要使用mutations?
在开发中,我们需要经常观察各个组件的状态,但是,我们又不能每次都通过console.log将状态输出到控制台进行查看,因此,我们需要通过devtools这个浏览器的插件来查看我们的组件状态,这个插件可以响应式的查看我们的状态的改变,并且将每一次状态的改变都记录下来。
在我们的Vuex中我们对公共状态的修改,官方给出的方法并不是我们直接通过$store.state.状态名 修改,直接通过$store.state.状态名 修改则会让我们的devtools不能检测到每一步counter的改变,这在开发中,就会造成,我们看着改变了,但是在插件显示中没有改变,那么,实际上,状态改变了还是没有改变呢?如果我们每次都需要核对一下这个的话,会很浪费时间,因此,我们需要尽量保证两者的一致。因此,我们尽量选择官方推荐的方式。
不推荐使用!!
<button @click="$store.state.counter++"></button>
实际上,官方要求我们想要在某一个组件中修改时,我们首先调用Dispatch[分发;发布]一个Actions;(当我们是同步修改时可省略),然后在这个Actions中通过commit[提交]将这个事件提交到mutations中,在mutations来修改State中的状态。
4.3.2.mutations的基础使用
1.首先,我们需要在store文件夹下的index.js文件中store对象的mutations中创建方法来对state中的状态进行修改 (篇幅问题,仅使用数字加进行展示)
注: 在mutations中的方法默认有一个参数:state,该参数代表的就是store对象中的状态保存对象state,我们可以通过state.状态名来对其进行操作。
mutations: {
//方法
//方法中默认有一个参数:state(对应的上面的state
incrementCount(state, count) {
state.counter++;
},
},
2.在需要修改共享状态的组件的行为中设置事件,例如,通过button的点击事件来体现
3.在该组件的methods中设置方法通过commit来提交所要调用的修改方法
<button @click="addition()">+</button>
addition() {
this.$store.commit('incrementCount')
},
4.4.getters的使用
红字部分为知识点
为什么要使用getters?
一个组件对共享状态进行某种运算,我们可以通过计算属性来解决,但是,如果有多个组件对这个运算结果都有需求,那么,我们就需要使用getters了,因为我们的getters只需要设置一次方法即可在多个组件中使用,而计算属性则是每个组件都要写相同的方法。
参数
第一个参数是state。第二个参数就是getters,getters参数可以让我们的该方法直接调用getters
作用
类似computed一样,如果我们想要的是将共享状态经过一定的运算之后的结果,那么我们就使用getters
优点
对于共享状态而言,如果我们使用computed来进行计算,则我们所有需要用到该计算结果的组件都需要写一行相同的代码,这无疑是很浪费以及麻烦的。但是如果我们使用的getters,对于不同的组件,我们只需要通过$store.getters.方法名来引用即可
4.4.1.getters的基本使用
目标1:将共享状态中的counter的平方显示
第一种.不使用getters 直接在组件中通过{{}}语法和$store.state.counter来实现
注意:这种使用,并没有修改State的值,因此可以使用。
<h2>{{ $store.state.counter * $store.state.counter }}</h2>
第二种.使用getters 步骤:
1.在store文件夹下的index.js文件中的store对象的getters属性 ->中创建一个方法,该方法也会默认传递一个参数state
getters: {
powerCounter(state) {
return state.counter * state.counter
}
},
2.在组件中通过$store.getters.powerCounter来调用显示
<h2>{{ $store.getters.powerCounter }}</h2>
4.4.2.getters作为参数使用和传递参数使用
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数
作为参数
目标2:有一组学生,我们需要将年龄大于等于20的学生信息挑出来进行显示
我们需要将保存学生信息的状态students在store文件夹下的index.js文件的state中保存。
students: [
{id: 110, name: 'why', age: 18},
{id: 111, name: 'kobe', age: 24},
{id: 112, name: 'james', age: 30},
{id: 113, name: 'curry', age: 10}
],
第一种.不使用getters 步骤:
1.我们直接在组件中的computed中创建一个计算属性来解决
more20stu() {
return this.$store.state.students.filter((s) => {
return s.age >= 20;
})
}
注:上方的代码有一种简写形式
more20stu() {
//简写形式
return this.$store.state.students.filter(s => s.age >= 20)
}
2.在组件中通过{{ more20stu }}来使用
<h2>{{ more20stu }}</h2>
缺点:
该种方法虽然也可以解决,但是当多个组件都需要用到该计算时,就需要我们在每个组件都写同样的这样的代码,不仅不宜维护,而且代码量大。
解决办法:使用getters
第二种.使用getters 步骤:
1.在getters中通过创建一个方法来实现
filter()就是一个筛选的功能,将满足条件的s作为返回值返回,(筛选条件)
more20stu(state) {
return state.students.filter(s => s.age >= 20)
},
2.在组件中通过$store.getters.more20stu来使用
<h2>{{ $store.getters.more20stu }}</h2>
目标3:我们想要知道age大于等于20的学生的人数
步骤:
1.在getters中通过创建一个方法来实现
more20stuLength(state) {
return state.students.filter(s => s.age >= 20).length
},
2.在组件中通过$store.getters.more20stuLength来使用
<h2>{{ $store.getters.more20stuLength }}</h2>
简化 (使用getters的参数)
事实上,我们在getters中创建的方法可以传递两个参数:
第一个参数是state。第二个参数就是getters,getters参数可以让我们的该方法直接调用getters
这样的话,上述目标3的步骤1,就可以简化成如下代码
more20stuLength(state, getters) {
return getters.more20stu.length
},
传递参数
目标4:
我们想要自己给出age,页面来显示学生中大于等于我们给出的age的学生.也就是说,我们想要的是可以灵活控制age,而不是写死的。
解决:
实际上,这也只是需要修改一下getters中的方法即可。
我们可以在getters创建的方法中return一个函数,并为该函数设置一个参数age(该参数需要我们调用时自己给出,我们在该函数中将其返回值设置成依据参数age来决定的即可
moreAgeStu(state) {
return function (age) {
return state.students.filter(s => s.age > age)
}
}
我们最后调用的时候,因为返回值是函数的问题,需要传递一个参数,也就是我们的age。
<h2>{{ $store.getters.moreAgeStu(12) }}</h2>
4.5.getters使用方式补充
技巧一 mapGetters
作用
将getters中的属性直接在组件中注册成computed,而不需要在组件的computed中再次命名并返回,也就是说,当我们注册完之后,我们就可以像使用computed一样使用我们getters
步骤
方法一
1.在组件中导入mapGetters(mapGetters方法在Vuex中)
import { mapGetters } from 'vuex'
2.在组件的computed中写入...mapGetters(['getters中的方法名'])
computed: {
...mapGetters(['cartLength'])
},
3.此时,我们就可以像使用computed一样使用我们getters了。
<div slot="center">购物车({{ cartLength }})</div>
方法二:(起别名)
1.在组件中导入mapGetters(mapGetters方法在Vuex中)
import { mapGetters } from 'vuex'
2.在组件的computed中写入...mapGetters({别名:'getters中的方法名'})
computed: {
...mapGetters({length: 'cartLength'})
},
技巧二 mapActions
作用
将methods中的方法映射到vuex的actions中,这样,在组件中需要进行dispatch一个actions的时候,就可以直接通过 this.别名 代替this.$store.dispatch传递给actions
步骤
方法一:
1.在组件中导入mapActions(mapActions方法在Vuex中)
import {mapActions} from "vuex";
2.在组件的methods中写入...mapActions(['methods中要提交给actions的方法名'])
methods: {
...mapActions(["addToCart"]),
}
3.此时,我们在组件中需要进行dispatch一个actions的时候,就可以直接通过 this.别名 代替this.$store.dispatch传递给actions
this.addToCart().then(res => {
})
因为:我们在使用 this.别名 的时候,mapActions在后面代替我们做了这件事情。
方法二: (起别名)
1.在组件中导入mapActions(mapActions方法在Vuex中)
2.在组件的methods中写入...mapGetters({别名:'getters中的方法名'})
4.6.Mutations(状态更新)
Vuex的store状态更新的唯一方式:提交mutations
mutations主要包括两部分:
1.字符串的事件类型
2.一个回调函数(handler),该回调函数的第一个参数就是state
mutations: {
incrementCount(state, count) {
state.counter++;
},
},
increment就是一个事件类型;其余部分为回调函数。
mutations传递参数: 通过mutations更新数据的时候,有可能我们希望携带一些额外的参数,参数被称为是mutations的载荷[payload]
4.6.1.mutations传递单个参数
目标1:
我们设置两个按钮,一个按钮可以每次+5,另外一个按钮每次+10
分析:
按照我们以往的思想,我们是给两个按钮设置不同的方法来调用。但是,这就带来了一个问题:代码量多大,以及代码复杂。但是,通过mutations的传递参数,我们可以给两个按钮设置同一个方法,之后再通过传递不同的参数来保证自己的作用不同
步骤:
1.首先,我们需要在组件中设置按钮的事件
<button @click="addCount(5)">+5</button>
<button @click="addCount(10)">+10</button>
2.在组件的methods中创建方法,并设置所要传递的参数并通过commit将其传递给mutations
addCount(count) {
this.$store.commit('incrementCount', count)
},
3.在store文件夹中的index.js文件的store对象通过mutations接收参数并通过参数对状态进行更改
incrementCount(state, payload) {
state.counter += payload.count
},
总结:
当我们只需要传递一个参数时,我们一般采用这种方式
4.6.2.mutations传递多个参数
目标2:我们为students添加一个元素
分析: 这也就意味着,我们传递的不仅仅是一个参数
步骤:
1.首先,我们需要在组件中的methods中创建一个方法,将我们要传递的多个参数放到一个对象中传递过去,用来传递这个元素以及告诉要传递给的mutations的事件类型
addStudent() {
const stu = {id: 114, name: 'alan', age: 35}
this.$store.commit('addStudent', stu)
},
2.在store文件夹中的index.js文件的store对象中通过mutations接收参数并通过参数对状态进行更改
addStudent(state, payload) {
state.students.push(payload.stu)
},
3.当界面中,有显示我们的状态时,更改后的状态会实时显示到界面中
总结:
当我们想要传递多个参数时,我们需要将这些参数封装到一个对象中传递,之后我们在mutations中再从对象中提取出相关信息即可
4.7.mutations的提交风格(两种)
1.通过commit进行提交是最普通的一种方式(普通方式)
2.Vue还提供了另外一种风格,它是一个包含了type属性的对象。这种方式提交,最终提交给mutations的是一个包含了所有东西的对象。
也就是说,Mutation中的处理方式是将整个commit的对象作为payload使用
type:指明了要提交给谁
addCount(count) {
//1.普通的提交封装
//this.$store.commit('incrementCount', count)
//2.特殊的提交封装
this.$store.commit({
type: 'incrementCount',
count,
})
},
此时,我们的mutations接收到的是一个对象,那么原来的那种接收方式就不合适了
原本:
incrementCount(state, count) {
state.counter += count
},
现在:
incrementCount(state, payload) {
state.counter += payload.count
},
4.8.Vuex数据的响应式规则
Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新 这要求我们必须遵守一写Vuex对应的规则:
1提前在store中初始化好所需的属性我们提前。定义好的属性会被加入到响应式系统中,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有界面中用到该属性的地方,让界面发生刷新。
举例:
1.我们在store的state中加入一个新的状态
info: {
name: 'kobe',
age: 40,
height: 1.98
}
2.我们在组件中设置一个按钮:该按钮用来修改info中name值
2.1.将info显示在界面上;设置按钮,并为按钮添加点击事件
<button @click="asyncUpdateName()">异步修改名字</button>
2.2.为点击事件绑定要执行的mutations
updataInfo() {
this.$store.commit('updataInfo')
},
2.3.在mutations中修改info的name
updataInfo(state) {
state.info.name = 'coderwhy'
}
结果: 当我们点击修改信息按钮之后,界面中的信息自动修改了,实现了响应式
2.4.在mutations中添加新的属性
updataInfo(state) {
state.info.name = 'coderwhy'
// 添加新属性
state.info['address'] = '洛杉矶'
}
结果: 当我们点击信息修改按钮之后,界面中并没有将该信息添加,也就是说,并不是响应式的
分析:
我们在devtools中可以看到事实上,我们已经将address添加到info中,但是并没有实时显示到页面上。这是由于,我们提前定义好的属性会被加入到响应式系统中,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有界面中用到该属性的地方,让界面发生刷新,但是我们后添加的address并没有提前定义好,因此,它没有被加入到响应式系统中,所以不能跟随界面刷新。
问题: 我们后添加的address并没有提前定义好,因此,它没有跟随界面刷新
解决:
当给state中的对象添加新属性时,使用下面的方式:
4.8.1.Vue.set
参数:
参数一:要修改的状态
参数二:为数组添加新属性,则写新属性的下标 为对象添加新属性,则写新属性的key
updataInfo(state) {
state.info.name = 'coderwhy'
Vue.set(state.info, 'address', '洛杉矶')//使用Vue.set()
}
4.8.2.新对象给就对象重新赋值
方式二:用新对象给旧对象重新赋值
state.info = {...state.info, "address": payload.address}
3.当给state中的对象删除属性时,使用下面的方式:
当我们直接使用delate删除对象属性时,虽然我们的实际数据发生改变,但是页面并不刷新,因此,直接使用delate并不是响应式。
1.使用Vue.delete() 参数:
参数一:要修改的状态
参数二:要删除的属性的键key或下标
Vue.delete(state.info, 'age')
4.9.Vuex的mutations的类型常量
在Vuex代码中,我们组件中的methods的指向和store下index.js文件中的类型名称必须一致,否则就会报错
问题:
当我们不是复制粘贴而是自己写时,就会经常出错,此类问题有不容易发现,费时费力
解决:
我们可以创建一个mutations-types.js文件,将我们用到的类型全都写道该文件中并为每一个类型都设置一个常量,这样我们在使用时通过导入的方式使用该常量
步骤:
1.创建一个mutations-types.js文件
2.将我们的类型在mutations-types.js文件中一一设置常量
export const INCREMENT = 'increment'
3.在组件和index.js文件中都导入需要使用的常量
import {INCREMENT} from "./matations-types";
4.将组件和index.js文件中用到该类型的地方用常量代替 组件:
需要注意的是,在index.js文件中使用常量的时候,我们还需要用[]把我们设置的常量名包裹起来。
//index.js
mutations: {
[INCREMENT](state) {
state.counter++
},
},
//组件中
addition() {
this.$store.commit(INCREMENT)
},
4.10.Vuex的Actions的使用详解:
为什么要使用Actions?
通常情况下,Vuex要求我们mutations中的方法必须是同步方法。主要原因是,当我们使用devtools时,devtools可以帮助我们捕捉mutations的快照 ,但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候完成。
我们以修改info的name为例。将原本的代码:
state.info.name = 'coderwhy'
修改为:
setTimeout(() => {
state.info.name = 'coderwhy'
}, 1000)
问题:
我们发现,虽然我们的界面上已经修改了,但是我们的devtools还是原来的name,这会使得我们调试代码时不知道是以界面为准还是devtools为准。因此,我们需要将两者的状态保持一致
解决办法:
Vuex官方告诉我们,当我们需要使用异步操作时,我们需要使用Actions来替代
步骤:
1.在store文件夹下的index.js文件中的store对象有一个actions。我们需要在actions中创建一个方法,该方法也有一个默认参数:context
actions: {
// 实现普通异步调用
aUpdateInfo(context) {
setTimeout(() => {
context.commit('updataInfo')
}, 1000)
}
},
2.修改我们组件中的methods,将其的指向修改为我们actions中的aUpdateInfo
updataInfo() {
//this.$store.commit('updataInfo')
//修改指向
this.$store.dispatch('aUpdateInfo')
}
注:
1.同样的,我们的actions中的方法也可以传递参数
1.1.组件:
updataInfo() {
this.$store.dispatch('aUpdateInfo', '我是payload')
}
1.2.actions:
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updataInfo')
console.log(payload);
}, 1000)
}
2.在我们异步操作执行成功后,我们需要返回一个提示,告诉用户执行完毕了
错误方式:
步骤:
2.1.在组件传递参数时,将payload参数传递为一个函数,该函数用来返回执行完毕信息
this.$store.dispatch('aUpdateInfo', () => {
console.log('里面已经完成了');
})
2.2.在store文件夹下index.js文件中的actions中,接收到该函数后直接调用
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updataInfo')
payload();
}, 1000)
}
问题:
这使得我们的传递的参数当作了执行完毕信息的函数,但是,如果我们还有其他的参数,这就导致我们的其他参数无法传递,很明显是有问题的。
方式一:
步骤:
1.在组件中传递参数时,将payload参数传递为一个对象,在对象中写入需要传递的信息外,再写一个函数,用来等完毕后调用
this.$store.dispatch('aUpdateInfo', {
message: '我是携带的信息',
success: () => {
console.log('里面已经完成了')
}
})
2.在store文件夹下index.js文件中的actions中,接受该payload。并在执行信息后,调用回调函数来返回执行完毕信息
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updataInfo')
console.log(payload.message);//执行信息
payload.success();//调用方法,传递执行完毕信息
}, 1000)
}
方式二:
步骤:
1.在store文件夹下index.js文件中的actions中通过返回一个Promise,然后调用Promise中的resolve来触发then来返回执行完毕信息
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updataInfo')
console.log(payload);
resolve('11111')
}, 1000)
})
}
2.在组件中除了传递参数以外,还需要调用then方法来实现返回
this.$store
.dispatch('aUpdateInfo', '我是携带的信息')
.then((result) => {
console.log('里面完成了提交');
console.log(result);
})
注意:在这个方法中,我们的promise是被返回给了组件,因此,我们的then方法是在组件中编写。
4.11.Vuex的modules的使用详解
作用
Vue使用单一状态树,这也就是说,很多状态都交给Vuex管理,当应用变得非常复杂时,store对象就有可能变得十分臃肿。因此,为了解决这个问题,Vuex允许我们将store分割成模块(Module),每个模块拥有自己的state、mutations、actions、getters等
注意:
虽然, 在模块中定义的东西都是定义在模块对象内部的,但是实际在调用的时候, 依然是通过this.$store来直接调用的。也就是说,我们在标签中的使用方式还是一样的。
4.11.1基础步骤
1.在store文件夹下index.js文件中创建一个moduleA或moduleB或...,在创建的moduleA中的可以设置自己的state状态
const moduleA = {
state: {},
getters: {},
mutations: {},
actions: {}
}
2.在store对象的modules属性中添加我们创建的moduleA
modules: {
a: moduleA
}
3.在组件中通过$store.state.a.状态名 来获取模块里state的某个状态
4.11.2.state步骤
1.在moduleA的state中添加一个状态
state: {
name: 'zhangsan'
}
2.在组件中通过$store.state.a.name获取模块里state的name
<h2>{{ $store.state.a.name }}</h2>
4.11.3.mutations步骤
1.在modeleA的mutations中添加一个方法
updateName(state, payload) {
state.name = payload
}
2.在组件中创建一个按钮,该按钮用来设置点击事件来传递参数
<button @click="updateName()">修改名字</button>
3.在组件的methods属性中创建updateName()方法用来传递参数
updateName() {
this.$store.commit('updateName', 'lisi')
}
4.11.4.getters步骤
1.普通方式
1.在modeleA的getters中添加一个方法
fullName(state) {
return state.name + '1111'
}
2.直接在组件中通过$store.getters.fullName来使用
<h2>{{ $store.getters.fullName }}</h2>
2.使用第二个参数getters
1.在modeleA的getters中添加一个方法
fullName2(state, getters) {
return getters.fullName + '2222'
}
2.直接在组件中通过$store.getters.fullName2来使用
<h2>{{ $store.getters.fullName2 }}</h2>
3.moduleA的getters的第三个参数
在moduleA的getters中,可以有个第三个参数:rootState。主要是为了让moduleA中的getters可以使用store对象中的state
1.在modeleA的getters中添加一个方法
fullName3(state, getters, rootState) {
return getters.fullName2 + rootState.counter
}
2.直接在组件中通过$store.getters.fullName3来使用
<h2>{{ $store.getters.fullName3 }}</h2>
4.11.5.actions步骤
1.在modeleA的actions中添加一个方法
aUpdateName(context) {
setTimeout(() => {
context.commit('updateName', 'wangwu')
}, 1000)
}
2.在组件中创建一个按钮,该按钮用来设置点击事件来传递参数
<button @click="asyncUpdateName()">异步修改名字</button>
3.在组件的methods属性中创建asyncUpdateName()方法用来传递参数
asyncUpdateName() {
this.$store.dispatch('aUpdateName')
}
注意:
我们在输出组件中的context后发现,在context中含有我们的rootGetters和rootState。也就是说,可以通过context.rootState和context.rootGetters来使用。
五、axios概念
5.1.常见的网络请求模块
5.1.1.网络模块的选择
Vue中发送网络请求有非常多的方式。
1.传统的Ajax是基于XMLHttpRequest(XHR)
为什么不用它?
1.配置和调用方式等非常混乱
2.编码起来看起来就非常乱
3.所以开发中很少直接使用,而是使用jQuery-Ajax
2.jQuery-Ajax,相对于传统Ajax非常好用
为什么不选择它?
首先,在Vue整个开发中都是不需要使用jQuery了,因为实际上jQuery的功能很多,但是我们的Vue开发使用到的却很少。这也就意味着,为了方便我们进行一个网络请求,特意引用一个完整的jQuery并不合理。jQuery的代码1W+行;Vue的代码也才1W+行。完全没有必要为了用网络请求就引用这个重量级框架
3.Vue-resource官方在Vue1.x的时候推出
1.Vue-resource的体积相对于jQuery小很多
2.Vue-resource是官方推出
为什么不选择它?
1.在Vue2.0推出后,Vue作者就在GitHub中的Issues中说明了去掉vue-resource,并且以后也不会更新
2.那么意味着以后vue-resource不在支持新的版本,也不会在继续更新维护
3.对以后的项目开发和维护都存在很大的隐患
4.axios在说明不在继续更新和维护vue-resource的同时,作者还推荐了一个框架:axios
5.2.JSONP的原理(了解)
使用JSONP最主要的原因往往是为了解决跨域访问的问题
JSONP的核心在于通过<script>标签的src来帮助我们请求数据。原因是项目部署在domain1.com服务器上的时候,是不能直接访问domain2.com服务器上的资料的。这个时候,我们利用<script>标签的src来帮助我们去服务器请求到数据,将数据当作一个JavaScript的函数来执行,并且执行的过程中传入我们需要的json,所以,封装jsonp的核心就在于我们监听window上的jsonp进行回调时的名称。
5.3.axios的优点
5.3.1.axios功能特点
1.在浏览器中发送XMLHttpRequests请求
2.在node.js中发送http请求
3.支持Promise API
4.拦截请求和响应
5.转换请求和响应数据
等等
5.3.2.axios请求方式
axios(config)
axios.request(config)
axios.get(url[,config])
axios.delete(url[,config])
axios.head(url[,config])
axios.post(url[,data[,config]])
axios.put(url[,data[,config]])
axios.patch(url[,data[,config]])
axios创建实例
axios拦截器的使用
5.3.3.发送并发请求
有时候, 我们可能需求同时发送两个请求。此时就需要使用axios.all, 可以放入多个请求的数组.
注意:
axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2
5.4.常见的配置选项
在开发中如果我们有一些参数是固定的。我们可以进行将这些参数抽取出来, 利用axiox的全局配置,将这些参数写入全局配置中。
1.请求地址 url: '/user'
2.请求类型 method: 'get'
3.请根路径 baseURL: 'http://www.mt.com/api?'
4.请求前的数据处理 transformRequest:[function(data){}]
5.请求后的数据处理 transformResponse:[function(data){}]
6.自定义的请求头 header: {'x-Requested-With': 'XMLHttpRequest'}
7.URL查询对象 params: {id: 12}
8.查询对象序列化 paramsSerializer: function(params){}
9.request body data: {key: 'aa'}
10.超时设置 timeout: 1000
11.跨域是否带Token withCredentials: false
12.自定义请求处理 adapter: function(resolve,reject,config){}
13.身份验证信息 auth: {uname: '', pwd: '12'}
14.响应的数据格式 responseType: 'json'
json/blob/document/arraybuffer/text/stream
六、axios的使用
6.1.基础使用方式
axios({
url: 'http://123.207.32.32:8000/home/multidata',
//默认为get请求,可以通过method来修改
//method: 'post'
}).then((result) => {
console.log(result);
})
// 我们可以通过params来确认查询对象
axios({
url: 'http://123.207.32.32:8000/home/data',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res);
})
6.2.发送并发请求
有时候, 我们可能需求同时发送两个请求。使用axios.all, 可以放入多个请求的数组.
axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2。
axios.all([
axios({
url: 'http://123.207.32.32:8000/home/multidata'
}),
axios({
url: 'http://123.207.32.32:8000/home/data',
params: {
type: 'sell',
page: 3
}
})
]).then(
results => {//这个和下面的数组展开使用一个即可
console.log(results);
console.log(results[0]);
console.log(results[1]);
}
//我们也可以使用axios.spread()来将数组展开
axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
})
)
6.3.使用全局的axios和对应配置进行网络请求
在上面的示例中, 我们的BaseURL是固定的。事实上, 在开发中可能很多参数都是固定的.这个时候我们可以进行一些抽取, 也可以利用axios的全局配置
axios.defaults.baseURL = 'http://123.207.32.32:8000';
axios.defaults.timeout = 5000
//通过全局设置后,我们的上面代码,可以修改为
axios.all([
axios({
url: '/home/multidata'
}),
axios({
url: '/home/data',
params: {
type: 'sell',
page: 3
}
})
]).then(
//我们也可以使用axios.spread()来将数组展开
axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
})
)
6.4.为什么要创建axios的实例呢?
当我们从axios模块中导入对象时, 使用的实例是默认的实例。当给该实例设置一些默认配置时, 这些配置就被固定下来了。
但是后续开发中, 可能会使用到别的配置。比如某些请求需要使用特定的baseURL或者timeout或者content-Type等。这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息。
七、axios封装
7.1.方案一
当我们调用axios的时候,一共需要给我们三个参数。
第一个参数就是我们要请求的数据的url
第二个参数就是,当我们请求成功的时候需要调用的函数
第三个参数是请求失败的时候调用的参数。
export function request(config, success, failure) {
//1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 8000
})
//2.发送真正的网络请求
instance(config)
.then(res => {
success(res)
})
.catch(err => {
failure(err)
})
}
//使用
request({
url: '/home/multidata'
}, res => {
console.log(res);
}, err => {
console.log(err);
})
7.2.方案二
基于方案一的优化,当调用axios的时候,需要只需要给我们一个对象即可,在对象中将我们需要的三个参数都放在对象中。
export function request(config) {
//1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 8000
})
//2.发送真正的网络请求
instance(config.baseConfig)
.then(res => {
config.success(res)
})
.catch(err => {
config.failure(err)
})
}
//使用
request({
baseConfig: {},
success: function (res) {
},
failure: function (err) {
}
})
7.3.方案三
基于方案二的优化,使用promise函数将,当我方案二包裹起来,并在网络请求的then方法中添加promise的resolve,在catch方法中添加reject。当网络请求成功后,进入then方法,执行resolve,这里的resolve,也就是then方法,需要我们在调用request的时候给出。
export function request(config) {
return new Promise((resolve, reject) => {
//1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 8000
})
//2.发送真正的网络请求
instance(config)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
}
//使用
request({
url: '/home/multidata'
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
7.4.最终方案(拦截器)
实际上,我们的方案三就已经实现了网络请求,但是,我们还有一些问题没有解决。比如,部分服务器对config又特殊要求,那么我们输入的config在向这个服务器请求数据的时候,就需要对config进行一定的处理,再请求。这个情况就可以由我们的拦截器来实现。
拦截请求:在发送请求的时候拦截。拦截的是config
拦截响应:在服务器响应的时候拦截。拦截的是请求到的data
需要注意的是,我们拦截成功之后,一定要将我们拦截的信息返回,不然信息被拦截了,也就没有所谓的网络请求了。
export function request(config) {
//1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 8000
})
//2.axios的拦截器
//全局拦截
//axios.interceptors
//2.1拦截请求的作用
//两个参数:都为函数
instance.interceptors.request.use(config => {
console.log(config);
//1.比如config中的一些信息不符合服务器要求,我们需要将config中的信息进行变化后再给服务器传递
//2.比如我们每次发送网络请求时都希望在界面中显示请求的图标
//3.某些网络请求是必须携带一些特殊信息,比如登录(token)
//注意:拦截之后一定在将其返回,不然拦截到之后,浏览器无法请求到信息
return config
}, err => {
console.log(err);
})
//2.2拦截响应
instance.interceptors.response.use(res => {
console.log(res);
//注意:拦截之后一定在将其返回,不然拦截到之后,浏览器无法请求到信息
return res.data
}, err => {
console.log(err);
})
//3.发送真正的网络请求
return instance(config)
}
//使用
request({
url: '/home/multidata'
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})