5 Promise 5.1 简介 ES6中一个非常重要和好用的特性就是Promise,但是初次接触Promise会一脸懵逼,这TM是什么东西?看看官方或者一些文章对它的介绍和用法,也是一头雾水。. Promise到底是做什么的呢?
Promise是异步编程的一种解决方案。那什么时候我们会来处理异步事件呢?
一种很常见的场景应该就是网络请求了。
我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。
所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。
如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。但是,当网络请求非常复杂时,就会出现回调地狱。
一个简单使用Promise异步多次请求网络资源的例子:
new Promise ((resolve, reject ) => { setTimeout (() => { resolve () }, 1000 ) }).then (() => { console .log ('Hello World!' ) return new Promise ((resolve, reject ) => { setTimeout (() => { resolve () }, 1000 ) }).then (() => { console .log ('Hello Vuejs!' ) return new Promise ((resolve, reject ) => { setTimeout (() => { resolve () }, 1000 ) }).then (() => { console .log ('Hello Python!' ) }) }) })
Q: 什么情况下会用到Promise?
A: 一般情况下,有异步操作时,使用Promise对这个异步操作进行封装
具体参数的一些说明:
new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('hello world' ) reject ('error message' ) }, 1000 ) }).then ((data ) => { console .log (data) }).catch (err => { console .log (err) })
5.2 Promise的三种状态和另外的处理方式 首先,当我们开发中有异步操作时,就可以给异步操作包装一个Promise,异步操作之后会有三种状态
pending: 等待状态,比如正在进行网络请求,或者定时器没有到时间
fulfill: 满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
reject: 拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
调用的小例子:
new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('Hello Vue.js' ) reject ('Error Message' ) }, 1000 ) }).then (data => { console .log (data) }, err => { console .log (err) })
Promise的链式调用
假设现在有如下需求:
// 网络请求: aaa -> 自己处理(10 行)// 处理:aaa111 -> 自己处理(10 行)// 处理:aaa111222 -> 自己处理
用Promise的最原始代码写法如下:
new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('aaa' ) }, 1000 ) }).then (res => { console .log (res, '第一层的10行处理代码' ) return new Promise ((resolve ) => { resolve (res + '111' ) }).then (res => { console .log (res, '第二层的10行处理代码' ) return new Promise (resolve => { resolve (res + '222' ) }).then (res => { console .log (res, '第三层的10行处理代码' ) }) }) })
要知道,Promise是支持直接调用.resolve()
方法,故上面的代码可以简写为:
new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('aaa' ) }, 1000 ) }).then (res => { console .log (res, '第一层的10行处理代码' ) return Promise .resolve (res + '111' ).then ( res => { console .log (res, '第二层的10行处理代码' ) return Promise .resolve (res + '222' ).then ( res => { console .log (res, '第三层的10行处理代码' ) }) }) })
事实上,就连Promise.resolve(都可以省略),直接return数据,es6解释器会在执行时自动加上Promise.resolve,故还可以进一步简写为:
new Promise ((resolve, reject ) => { setTimeout (() => { resolve ('aaa' ) }, 1000 ) }).then (res => { console .log (res, '第一层的10行处理代码' ) return res + '111' }).then ( res => { console .log (res, '第二层的10行处理代码' ) return res + '222' }).then ( res => { console .log (res, '第三层的10行处理代码' ) })
6 Vuex 6.1 Vuex简介 Vuex是做什么的
Vuex是一个专为Vue.js应用程序开发的状态管理模式
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
Vuex也集成到Vue的官方调试工具devtools,提供了诸如零配置的time-travel调试,状态快照导入导出等高级调试功能
状态管理到底是什么
状态管理模式,集中式存储管理这些名词看起来就非常高大上,让人捉摸不透
其实,你可以简单的将其看成把需要多个组件共享的全局变量全部存储在一个对象里面
然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢
为什么需要使用专门的Vuex而不自己写个对象实现呢
当然可以,但是我们要先想想VueJS带给我们最大的便利是什么?没错,就是响应式
如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些
不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了
管理什么状态呢
如果你做过大型项目开发,你一定遇到过多个状态在多个界面间共享的问题,如:
用户的登录状态、用户名称、头像、地理位置信息
商品的收藏,购物车中的物品等
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
6.2 页面的状态管理 我们知道,要在单个组件中进行状态管理是一件非常简单的事情。什么意思呢?我们来看下面的图片。
这图片中的三种东西,怎么理解呢?
State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)
View :视图层,可以针对State的变化,显示不的信息。(这个好理解吧? )
Actions :这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
单页面状态管理的实现
<template > <div class ="test" > <div > 当前计数:{{counter}}</div > <button @click ="counter+=1" > +1</button > <button @click ="counter-=1" > -1</button > </div > </template > <script > export default { name : 'HelloWorld' , data ( ) { return { counter : 0 } } } </script > <style scoped > </style >
在这个案例中,我们有木有状态需要管理呢?没错,就是个数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状态管理图例
注意:
不要在组件中直接修改state(组件->state),需要走组件-> action-> mutations->state修改state,这样devtools可以跟踪到修改,方便调试
官方是允许直接修改mutations的(即组件->mutations->state)
但mutations中的修改一般是同步操作,异步操作需要通过actions修改,否则Vue的调试工具devtools没有办法跟踪到,不方便调试
6.3 使用Vuex 以下是使用Vuex的一个小例子,用于改变count值:
@/store/index.js
import Vue from 'vue' import Vuex from 'vuex' Vue .use (Vuex )const store = new Vuex .Store ({ state : { counter : 0 }, mutations : { increment (state) { state.counter ++ }, decrement (state) { state.counter -- } }, actions : { }, getters : { }, modules : { } })export default store
App.vue
<template > <div id ="app" > <p > {{count}}</p > <button @click ="addition" > +</button > <button @click ="subtraction" > -</button > </div > </template > <script > export default { name : 'App' , data () { return { message : '我是App组件' } }, computed : { count () { return this .$store .state .counter } }, methods : { addition () { this .$store .commit ('increment' ) }, subtraction () { this .$store .commit ('decrement' ) } } } </script > <style > </style >
@/main.js
import Vue from 'vue' import App from './App' import router from './router' import store from '@/store' Vue .config .productionTip = false new Vue ({ el : '#app' , router, store, components : { App }, template : '<App/>' })
好的,这就是使用Vuex最简单的方式了。
做一下简单的小节:
提取出一个公共的store对象,用于保存在多个组件中共享的状态
将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
在其他组件中使用store对象中保存的状态即可
通过this.Sstore.state.属性的方式来访问状态
通过this.$store.commit(‘mutation中方法)来修改状态
注意事项:
我们通过提交mutation的方式,而非直接改变store.state.count
这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count
的值。
6.4 Vuex核心概念 6.4.1 State及Getters State单一状态树
Vuex提出使用单一状态树,什么是单一状态树呢?
英文名称是single Source of Truth ,也可以翻译成单一数据源。
但是,它是什么呢?我们来看一个生活中的例子。
OK,我用一个生活中的例子做一个简单的类比。
我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
这个和我们在应用开发中比较类似:
如果你的状态信息是保存到多个Store对象中的, 那么管理和维护将会变得非常困难
所以Vuex也使用了单一状态树来管理应用层级的全部状态
单一状态树能够让我们最直接的方式找到某个状态片段,而且在之后的维护和调试中,也可以方便的管理和维护
关于state和getters一个例子:
@store/index.js
import Vue from 'vue' import Vuex from 'vuex' Vue .use (Vuex )const store = new Vuex .Store ({ state : { students : [ { id : 1 , name : 'kobe' , age : 37 }, { id : 2 , name : 'James' , age : 34 }, { id : 3 , name : 'Nash' , age : 33 } ] }, mutations : {}, actions : {}, getters : { more20stu (state) { return state.students .filter (s => s.age > 20 ) }, more20stuLength (state, getters) { return getters.more20stu .length } }, modules : {} })export default store
@App.vue
<template > <div id ="app" > <p > {{$store.getters.more20stu}}</p > <p > {{$store.getters.more20stuLength}}</p > </div > </template >
6.4.2 Mutations Mutations
Vuex的store状态的更新的唯一方式: 提交Mutation
Mutation主要包括两部分:
Mutation传递参数
在通过mutation更新数据的时候,有可能我们希望携带一些额外的参数,这些参数被称为是mutation的载荷(payload)
Mutation中的代码:
decrement (state, n ) { state.count -= n } 。。。。decreament : function ( ) { this .$store .commit ('decrement' , 2 ) }
但是如果参数不是一个呢?
Mutation提交风格
上面的通过commit进行提交是一种普通的方式,Vue还提供了另外一种风格, 它是一个包含type属性的对象
this .$store .commit ({ type : 'changeCount' , count : 100 })
addCount (count) { this .$store .commit ({ type : 'incrementCount' , payload }) }changeCount (state, payload ) { state.count = payload.count }
Mutation的响应规则
Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新,这就要求我们必须遵守一些Vuex对应的规则:
提前在store中初始化好所需的属性
当给state中的对象新属性时,使用下面的方式:
使用Vue.set(obj, ‘newProp’, 123)
用新对象给旧对象重新赋值
state.obj = { ...state.obj , newProp : 123 }
通过这种方式添加元素,是不能在页面上做到实时响应式的:
state .info['address'] = "Los Angeles"
如果要做到响应式,需要通过这种方式:
// 添加 Vue.set (state .info, 'address', 'Los Angeles') // 删除 Vue.delete(state .info, 'age')
通常情况下,Vuex要求我们mutation中的方法必须是同步方法
主要的原因是当我们使用devtools时,devtools可以帮助我们不做mutation的快照
但是如果是异步操作,那么devtools将不能很好的最终这个操作什么时候完成
我们强调,不要在mutation中进行异步操作
但是某些情况,我们确实希望在Vuex中进行一些异步操作,比如网络请求
这事就需要使用action来代替mutation进行异步操作
6.4.3 Actions 来一段代码的例子吧:
@/store/index.js
...const store = new Vuex .Store ({ state : { info : 'kobe' }, mutations : { updateInfo (state) { state.info = 'James' } }, actions : { aUpdateInfo (context, payload) { setTimeout (() => { console .log (payload) context.commit ('updateInfo' ) }) } } ...
@/app.vue
<template > <div id ="app" > <p > {{$store.state.info}}</p > <button @click ="updateInfo" > change</button > </div > </template > <script > export default { name : 'App' , data () { return { message : '我是App组件' } }, methods : { updateInfo () { this .$store .dispatch ('aUpdateInfo' , '我是payload' ) } } } </script >
6.4.4 Module Module是模块的意思,为什么在Vuex中我们要使用模块呢?
Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
当应用变得非常复杂时,store对象就有可能变得相当壁肿.
为了解决这个问题, Vuex允许我们将store分割成模块(Module),而每个模块拥有自己的state, mutation.action, getters等
const store = new Vuex .Store ({ state : { counter : 0 , info : 'kobe' }, mutations : { increment (state) { state.counter ++ }, decrement (state) { state.counter -- }, incrementCount (state, count) { state.counter += count }, updateInfo (state) { state.info = 'James' } }, actions : {}, getters : {}, modules : { a : moduleA } })
const moduleA = { state : { name : 'Allen' }, mutations : { updateName (state, payload) { state.name = payload } }, actions : { aUpdateName (context) { context.commit ('updateName' , 'Yao' ) } }, getters : { fullName (state) { return state.name + ' Team' }, fullName2 (state, getters) { return getters.fullName + ' has win ' }, fullName3 (state, getters, rootState) { return getters.fullName2 + rootState.counter + ' times' } } }
<template > <div id ="app" > <h2 > ----------------App内容: modules中的内容--------</h2 > <h2 > {{$store.state.a.name}}</h2 > # Allen <p > {{$store.getters.fullName}}</p > # Allen Team <p > {{$store.getters.fullName2}}</p > # Allen Team has win <p > {{$store.getters.fullName3}}</p > # Allen Team has win 0 times <button @click ="updateName" > 修改名字</button > </div > </template > <script > export default { name : 'App' , data () { return { message : '我是App组件' } }, methods : { updateName () { this .$store .commit ('updateName' , 'Curry' ) } } } </script > <style > </style >
notes:
此处需要注意,当需要访问子模块state中的name时,需要使用$store.state.a.name
这种形式,因为实际上,Vuex会把子模块的state作为一个对象放入root的state中,类似{a: {子模块state内容}}这种形式
6.4.5 补:Vuex的项目结构 当我们的Vuex帮助我们管理过多的内容时,好的项目结构可以让我们的代码更清晰:
store| index.js # 我们组装模块并导出store的地方 | actions.js # 根级别的action | mutation.js # 根级别的mutation | modules | cart.js # 购物车模块 | products.js # 产品模块
7 Axios 几种方式的对比 ajax: 【优点 :局部更新;原生支持】 【缺点 :可能破坏浏览器后退功能;嵌套回调】 jqueryAjax: 【在原生的ajax的基础上进行了封装;支持jsonp】 fetch: 【优点 :解决回调地狱】 【缺点 :API 偏底层,需要封装;默认不带Cookie,需要手动添加; 浏览器支持情况不是很友好,需要第三方的ployfill】 axios: 【几乎完美】
axios
的特点 支持浏览器和node.js 支持promise 能拦截 请求和响应 能转换请求和响应数据 能取消 请求 自动转换JSON数据 浏览器端支持防止CSRF (跨站请求伪造)
发送get请求演示
import axios from 'axois' export default { name : 'app' , created () { axios.get ('http://httpbin.org' ).then ( res => { console .log (res) } ).catch (err => { console .log (err) }) } }
发送并发请求
有时候我们可能需要同时发送两个请求,使用axios.all, 可以放入多个请求的数组
axios.all([])返回的结果是一个数组,使用axios.spreed可将数组[res1, res2] 展开为res1, res2
import axios from 'axios' export default { name : 'app' , created ( ) { axios.all ( [ axios.get ('http://123.207.32.32:8000/category' ), axios.get ('http://123.207.32.32:8000/home/data' , {params : {type : 'sell' , page : 1 }}) ] ).then (axios.spread (res1, res2) => { console .log (res1) console .log (res2) }) } }
全局配置
在上面的示例中,我们的BaseURL是固定的
事实上,在开发中可能很多参数都是固定的
这个时候我们可以进行一些抽取,也可以利用axios全局配置
axios.defaults .baseURL = '123.207.32.32:8000' axios.defaults .header .post ['Content-Type' ] = 'application/x-www-form-urlencoded'
常见的配置选项
请求地址:url: ‘/user’
请求类型: method: ‘get’
请求路径:baseURL: ‘http://www.mt.com/api'
请求前的数据处理: transformRequestL [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’
封装axios实例
import axios from 'axios' export function request (config, success, failure) { const instance = axios.create ({ baseURL : 'http://123.207.32.32:8000' , timeout : 5000 }) instance (config).then (res => { success (res) }).catch (err => { failure (err) }) }export function request (config) { return new Promise ((resolve, reject ) => { const instance = axios.create ({ baseURL : 'http://123.207.32.32:8000' , timeout : 5000 }) instance (config).then (res => { resolve (res) }).catch (err => { reject (err) }) }) }export function request (config) { const instance = axios.create ({ baseURL : 'http://123.207.32.32:8000' , timeout : 5000 }) return instance (config) }
request ({ url : '/home/multidata' }).then (res => { console .log (res) })
拦截器
axios提供了拦截器,用于我们在发送每次请求或者得到响应后,进行对应的处理
如何使用呢?
export function request (config) { const instance = axios.create ({ baseURL : 'http://123.207.32.32:8000' , timeout : 5000 }) instance.interceptors .request .use (config => { console .log (config) return config }, err => { console .log (err) }) instance.interceptors .response .use (res => { console .log (res) return res.data }, err => { console .log (err) }) return instance (config) }