Promise
Promise是异步编程的一种解决方案,能有效避免回调地狱。
Promise的基本使用
new Proise((resolve, reject) => {
setTimeout(() => {
resolve('Hello World');
reject('Error Data');
}, 1000)
}).then(data => {
console.log(data); // Hello World
}).catch(error => {
console.log(error); // Error Data
})
Promise的三种状态:
- pending:等待状态,比如正在进行网络请求或者定时器没有到时间
- fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
- reject:拒绝状态,当我们主动回调了reject时,就处于改状态,并且会回调.catch()
Promise的链式调用
Promise回调then或者catch方法时都会返回一个Promise对象,所以我们可以对其进行链式调用,通过Promise对新数据进行包装并返回。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello World');
}, 1000)
}).then(data => {
console.log(data); // Hello World
return Promise.resolve(data + '1');
}).then(data => {
console.log(data); // Hello World1
return Promise.reject(data + 'error');
}).then(data => {
console.log(data); // 这里没有输出,这部分代码不会执行
return Promise.resolve(data + '2');
}).catch(error => {
console.log(data); // Hello World1error
return Promise.resolve(data + '3');
}).then(data => {
console.log(data); // Hello World1error3
})
// 可直接通过return data代替Promise.resolve进行简写
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello World');
}, 1000)
}).then(data => {
console.log(data); // Hello World
return data + '1';
}).then(data => {
console.log(data); // Hello World1
return Promise.reject(data + 'error');
}).then(data => {
console.log(data); // 这里没有输出,这部分代码不会执行
return data + '2';
}).catch(error => {
console.log(data); // Hello World1error
return data + '3';
}).then(data => {
console.log(data); // Hello World1error3
})
Promise.all
Promise.all([
new Promise((resolve,reject) => {
setTimeout(() => {
resolve('result1')
}, 1000)
}),
new Promise((resolve,reject) => {
setTimeout(() => {
resolve('result2')
}, 2000)
})
]).then(results => {
console.log(results); // ['result1', 'result2']
})
Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理模式。
- 它采用集中式存储管理应用的所有组件的状态,并以对应的规则保证状态以一种可预测的方式发生变化。
- Vuex也集成到Vue的官方调试工具devtools extension,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能。
单界面的状态管理
单个组件中进行状态管理是一件非常简单的事件:
- state:状态
- View:视图层,针对status的变化显示不同的信息
- Actions:导致状态改变的操作:点击、输入等
<template>
<div class="test">
<div>当前计数:{{counter}}</div>
<button @click="counter++">+1</button>
<button @click="counter--">-1</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
counter: 0
}
}
}
</script>
// 解析:
- status: counter
- View: 对应的页面显示
- Actions:按钮的点击事件
多界面的状态管理
当某些状态属于的多个界面共同维护时,我们就需要用到Vuex。Vuex的基本思想:将共享的状态抽取出来,每个视图按照规定进行访问和修改操作。
Vuex状态管理图例
简单的案例
- 创建一个文件夹store,并在其中新建一个index.js文件
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vue.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count ++;
},
decrement(state) {
state.count --;
}
}
})
export default store
- 挂载到Vue实例中
import Vue form 'vue'
import App form './App'
import stor form './store'
new Vue({
el: '#app',
store,
render: h => h(app)
})
- 使用Vuex的count
<template>
<div class="test">
<div>当前计数:{{counter}}</div>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
computed: {
counter() {
return this.$store.state.count
}
},
methods: {
increment() {
this.$store.commit('increment')
},
decrement() {
this.$store.commit('decrement')
}
}
}
</script>
- 小结:
-
- 提取一个公共的store对象,用于保存在多个组件中共享的状态。
-
- 将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
-
- 在其他组件中使用store对象中保存的状态即可;
- 通过this.$store.state.属性的方法来访问状态
- 通过this.$store.commit('mutation中方法名')来修改状态
- 在其他组件中使用store对象中保存的状态即可;
-
- 注意事项:
- 通过提交mutation的方式而非直接改变store.state.count,这是因为Vuex可以更明确追踪状态的变化。
Vuex的核心概念
- State
- Getters
- Mutation
- Action
- Module
State 单一状态树
用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
Getters
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
基本使用
const store = new Vue.Store({
state: {
students: [
{id: 110, name: 'apple', age: 18},
{id: 111, name: 'banana', age: 21},
{id: 112, name: 'orange', age: 25}
]
},
getters: {
// 获取年龄大于20的学生
greaterAgesStus: state => {
return state.students.filter(s => s.age >= 20).length
},
// 获得年龄大于20的学生
greaterAgesCount: (state, getters)=> {
return getters.greaterAgesStus.length
},
// getters默认是不能传递参数的,如果希望传递参数,则只能让getters本身返回一个函数。
// 根据id获取学生信息
stuById: state => {
return id => {
return state.students.find(s => s.id === id);
}
}
}
})
Mutation
Vuex的store状态更新的唯一方式:提交Mutations Mutations主要包括两个部分:
- 字符串的事件类型(type)
- 回调函数(handler),该回调函数的第一个参数就是state
基本用法
// 定义
mutations: {
increment(state) {
state.count ++
}
}
// 调用
increment() {
this.$state.commit('increment');
}
参数传递
在通过mutation更新数据时,可以携带额外的参数,这些参数被称为mutation的载荷(Payload)。
/* 单个参数传递 */
// 定义
mutations: {
decrement(state, n) {
state.count -= n;
}
}
// 调用
decrement() {
this.$store.commit('decrement', 2);
}
/* 多个参数传递通过对象 */
// 定义
mutations: {
decrement(state, payload) {
state.count -= payload.count;
}
}
// 调用
decrement() {
this.$store.commit('decrement', {count: 2});
}
提交风格
Vue还提供了另外一种风格,它是一个包含type属性的对象
this.$store.commit({
type: 'decrement',
count: 100
})
// Mutations的处理方式是将整个commit的对象作为payload使用,所以代码没有改变
mutations: {
decrement(state, payload) {
state.count -= payload.count;
}
}
响应规则
Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新。这就要求我们遵守Vuex对应的规则:
- 提前在store中初始化好所需的属性
- 当给state中的对象添加新属性时,使用下面的方式:
- 方式一:使用Vue.set(obj, 'newProp', 123)
- 方式二:用新对象给旧对象重新赋值
mutations: {
updateInfo(state, payload) {
// 界面不会自动更新
state.info['height'] = payload.height;
// 方式一:
Vue.set(state.info, 'height', payload.height);
// 方式二
state.info = {...state.info, 'height': payload.height};
}
}
常量类型
问题:
在mutations中, 我们定义了很多事件类型(也就是其中的方法名称).
当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutations中的方法越来越多.
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
解决方法:
使用常量替代Mutations事件的类型.
我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
// 创建一个文件:mutation-types.js
export const UPDATE_INFO = 'UPDATE_INFO'
// 在store中引入
import Vuex from 'vuex'
import Vue from 'vue'
import * as types from './mutation-types'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
info: {
name: 'apple',
age: 18
}
},
mutations: {
[types.UPDATE_INFO](state, payload) {
state.info = {...state.info, 'height': payload.height};
}
}
})
export default store
// 在组件中调用
import {UPDATE_INFO} from './mutation-types';
methods: {
updareInfo() {
this.$store.commit(UPDATE_INFO, {height: 1.88})
}
}
同步函数
通常情况下,Vuex要求我们Mutations中的方法必须时同步方法。
主要的原因时当我们使用devtools时,devtools可以帮助我们捕捉mutations的快照,如果时异步操作,那么devtools将不能很好的跟踪这个操作什么时候会被完成。
Action
我们强调在Mutations中不允许进行异步操作,在某些情况下必须进行异步操作则可用Action来代替Mutations进行异步操作。
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
基本使用
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count ++
}
},
actions: {
increment(context) {
context.commit('increment')
}
}
})
注意: Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。
分发(dispatch)
在Vue组件中,如果我们调用action中的方法,那么就需要使用dispatch。
methods: {
increment() {
this.$store.dispatch('increment');
}
// 支持参数传递payload
increment() {
this.$store.dispatch('increment', {count: 2});
}
}
// 定义 payload
mutations: {
increment(state, payload) {
state.count -= payload.count
}
},
actions: {
increment(context, payload) {
setTimeout( () => {
context.commit('increment', payload)
}, 5000)
}
}
异步Promise返回
在Action中我们可以将异步操作放在一个Promise中,并且在成功或者失败后,调用对应的resolve或reject
// 定义
actions: {
increment(context) {
return new Promise(resolve => {
setTimeout(() => {
context.commit('increment');
resolve()
}, 5000)
})
}
}
// 调用
methods: {
increment() {
this.$store.dispatch('increment').then(res => {
console.log('完成了更新操作');
});
}
}
Module
问题:
Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
当应用变得非常复杂时,store对象就有可能变得相当臃肿.
解决方法:
Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutation、action、getters等
基本使用
const moduleA = {
state: {
count: 0
},
mutations: {
increments(state) {
state.count ++
}
},
actions: {
// context -> {state, commit, rootState};
incrementIfOnRootSum({state, commit, rootState}) {
if((state.count + rootState.count) % 2 === 1) {
commit('increment');
}
}
},
getters: {
doubleCount(state) {
return state.count * 2
},
sumWithRootCount(state, getters, rootState) {
return state.count + rootState.count;
}
}
}
const moduleB = {
state: {},
mutations: {},
actions: {},
getters: {}
}
const store = new Vuex.Store({
state: {
gcount: 1
},
modules: {
a: moduleA,
b: moduleB
}
})
store.modules.a // -> moduleA的状态state
store.modules.a.count // 0
store.modules.b // -> moduleB的状态state
// 在组件内使用
<script>
export default {
name: 'App',
computed: {
count() {
return this.$store.getters.doubleCount
}
},
methods: {
increment() {
this.$store.commit('increment')
}
}
}
</script>
// 注意: 虽然doubleCount和increment都是定义在对象内部的,但是在调用的时候,依然是通过this.$store来直接调用的。
目录结构
当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰.
网络模块封装
网络模块的选择
- 传统的Ajax是基于XMLHttpRequest(XHR)
- 配置和调用方式等非常混乱
- jQuery-Ajax
- 在Vue的整个开发中都是不需要使用jQuery
- 为了一个网络请求而引入jQuery是不合理的,体积太大
- Vue-resource
- vue-resource不再支持新版本,对以后的项目开发和维护都存在很大的隐患
- axios
- 在浏览器中发送XMLHttpRequests请求
- 在node.js中发送http请求
- 支持Promise API
- 拦截请求和响应
- 转换请求和响应数据
axios请求方式
- axios(config)
- axios.request(config)
- axios.get(url[, config])
- axios.delete(url[, config])
- axios.head(url[, config])
- axios.post(url[, config])
- axios.put(url[, config])
- axios.patch(url[, config])
axios.get
import axios from 'axios'
export default {
name: 'App',
created() {
axios.get('http://123.207.32.32:8000/category')
.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
});
axios.get('http://123.207.32.32:8000/home/data',
{params: {type: 'sell', page: 1}})
.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
}
}
发送并发请求
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);
}))
}
}
全局配置
import axios from 'axios'
export default {
name: 'app',
created() {
// 提取全局的配置
axios.defaults.baseURL = 'http://123.207.32.32:8000';
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
// 发送并发请求
axios.all([axios.get('/category'),
axios.get('/home/data',
{params: {type: 'sell', page: 1}})])
.then(axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
}))
}
}
常见的配置选项
- 请求地址
- url: '/user'
- 请求类型
- method: 'get'
- 请求根路径
- baseURL: 'www.mt.com/api'
- 请求前的数据处理
- transformRequest: [function(data){}]
- 请求后的数据处理
- transformResponse: [function(data){}]
- 自定义的请求头
- headers: {'x-Requested-With': 'XMLHttpRequest'}
- URL查询对象
- params: {id: 2}
- 查询对象序列化函数
- paramsSerializer: function(params){}
- post request body
- data: {key: 'aa'}
- 超时设置
- timeout: 1000
- 跨域是否自带Token
- withCredentials: false
- 自定义请求处理
- adapter: function(resolve, reject, config)
- 身份验证信息
- auth: {uname: '', pwd: '12'}
- 响应的数据格式
- responseType: 'json'
- json/blob/document/arraybuffer/text/stream
axios实例
我们从axios模块中导入对象时,使用的是默认的实例,当设置一些默认配置时,这些配置就被固定下来了,当某些请求需要一些区别于默认配置的特殊配置时,我们可以创建新的实例,并且配置专属的配置信息。
const axiosInstance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
axiosInstance({
url: '/category',
method: 'get'
}).then(res => {
}).catch(err => {
})
axios封装
import originAxios from 'axios'
export default function axios(option) {
return new Promise((resolve, reject) => {
// 1. 创建axios实例
const instance = originAxios.create({
baseURL: '/api',
timeout: 5000,
headers: ''
});
// 2. 传入对象进行网络请求
instance(option).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
拦截器
axios提供了拦截器,用于我们在每次请求或者得到响应后进行对应的处理。
instance.interceptors.request.use(config => {
console.log('来到了request拦截success中')
//1. 发送网络请求时,在页面添加一个loading组件
//2. 某些请求要求用户必须登陆,判断用户是否有token,如果没有token跳转到login页面
//3. 对请求的参数进行序列化
config.data = qs.stringify(config.data)
return config
},err => {
console.log('来到了request拦截failure中')
// 错误拦截比较少,例如请求超时,跳转到错误页面
return err
})
instance.interceptors.response.use(response => {
console.log('来到了response拦截success中')
// 1. 响应的成功拦截主要是对数据进行过滤
return response.data
}, err => {
console.log('来到了response拦截failure中')
// 响应的失败拦截中,可以根据status判断报错的错误码,跳转到不同的错误提示页面
if(err && err.response) {
switch(err.response.status) {
case 400:
err.message = '请求错误'
break;
case 401:
err.message = '未授权的访问'
break;
}
}
return err
})
axios({
url: '/home/data',
method: 'get',
params: {
type: 'sell',
page: 1
}
}).then(res => {
}).catch(err => {
})
// 输出结果
来到了request拦截success中
来到了response拦截success中