ES6补充 - Promise
Promise 用来做什么的
Promise到底是做什么的呢?
Promise是异步编程的一种解决方案。
那什么时候回来处理异步事件呢?
- 一种很常见的场景就是网络请求了
- 我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的 3+4=7 一样将结果返回
- 所以往往我们会传入另一个函数,在数据请求成功时,将数据通过传入的函数回调出去
- 如果只是一个简单的网络请求,那么这种方案不会带来很大的麻烦
但是,当网络请求非常复杂时,就会出现回调“地狱”(函数作为参数层层嵌套)
我们拿一个非常夸张的案例
网络请求的回调‘地狱’
我们看下下面的场景
- 我们需要通过一个url1 从服务器加载一个数据 data1, data1中包含了下一个请求url2
- 我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
- 发送网络请求url4,获取最终的数据data4
上面的有什么问题吗?
- 正常情况下,不会有什么问题,可以正常运行并且获取想要的结果。
- 但是,这样代码难看而且不容易维护。
- 我们更加期望的是一种更加优雅的方式来进行这种异步操作。
如何做呢?就是使用
Promise。
Promise
可以以一种非常优雅的方式来解决这个问题
Promise 的基本使用
我们先来看看 Promise 最基本的语法
这里,我们用一个定时器来模拟异步事件
- 假设下面的data是从网络上1秒后请求的数据
console.log
就是我们的处理方式
- 下面是 Promise 代码
<script>
new Promise( (resolve, reject) => {
setTimeout(function() {
resolve('hello world')
reject('error data')
}, 1000)
}).then( (data) => {
console.log(data);
}).catch( (error) => {
console.log(error);
})
</script>
这个例子会让我们觉得 多此一举
- 首先,下面的
Promise
代码中明显比上面的代码看起来还要复杂。- 其次,下面的
Promise
代码中包含的resolve、reject、then、catch
都是什么意思?
定时器异步事件解析
我们来认真的读一下这个程序能干嘛?
new Promise
很明显是创建一个Promise对象- 小括号中
((resolve, reject) => {})
也很明显就是一个函数,而且我们这里用的是箭头函数。
- 但是
resolve, reject
它们是什么呢?- 我们先知道一个事实:在创建Promise时,传入的这个箭头函数是固定的(一般我们都会这样写)
resolve
和reject
它们两个也是函数,通常情况下,我们会根据请求数据的成功和失败来决定调用哪一个。
- 成功还是失败?
- 如果是成功的,那么通常我们会调用
resolve(messsage)
,这个时候,我们后续的then会被回调。- 如果是失败的,那么通常我们会调用
reject(error)
,这个时候,我们后续的catch会被回调。ok,到这里你已经了解了
Promise
的基本使用
Promise 的三种状态
我们先来看下这 三种状态
-
pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
-
fulfill:满足状态,当我们主动回调了
resolve
时 ,就处于这种状态,并且回调. then()
-
reject:拒绝状态,当我们主动回调了
reject
时,就处于该状态,并且会回调.catch()
Promise 的链式调用
我们在看Promise的流程图时,发现无论是
then
还是catch
都可以返回一个Promise对象
所以,我们的代码其实是可以进行链式调用的:
这里我们直接通过Promise包装了一下新的数据,将Promise对象返回了
Promise.resovle():
将数据包装成Promise对象,并且在内部回调resovle()
函数Promise.reject():
将数据包装成Promise对象,并且在内部回调reject()
函数
<script>
new Promise((resolve, reject) => {
// setTimeout 我们当做网络请求
setTimeout(() => {
resolve('hello world')
}, 1000);
}).then(data => { // 箭头函数 只有一个参数的时候 可以不写括号
console.log(data); // hello world
return Promise.resolve(data + '111')
}).then(data => {
console.log(data); //hello world111
return Promise.resolve(data + '222')
}).then(data => {
console.log(data); //hello world111222
return Promise.reject(data + 'error')
// 这里回调了 reject
}).then(data => {
console.log(data); // 这里没有输出 ,这部分代码不会执行
return Promise.resolve(data + '333')
}).catch(data => {
console.log(data); //hello world111222error
return Promise.resolve(data + '444')
}).then(data => {
console.log(data); //hello world111222error444
})
</script>
链式调用简写
我们来简写 代码
- 如果我们希望数据直接包装成
Promise.resolve
,那么在then
中可以直接返回数据- 注意下面的代码中,我把
return Promise.resovle(data)
改成了return data
结果依然是一样的
<script>
new Promise((resolve, reject) => {
// setTimeout 我们当做网络请求
setTimeout(() => {
resolve('hello world')
}, 1000);
}).then(data => { // 箭头函数 只有一个参数的时候 可以不写括号
console.log(data); // hello world
return data + '111'
}).then(data => {
console.log(data); //hello world111
return data + '222'
}).then(data => {
console.log(data); //hello world111222
return Promise.reject(data + 'error')
// 这里回调了 reject
}).then(data => {
console.log(data); // 这里没有输出 ,这部分代码不会执行
return data + '333'
}).catch(data => {
console.log(data); //hello world111222error
return data + '444'
}).then(data => {
console.log(data); //hello world111222error444
})
</script>
Promise 的all方法
使用场景 当一个需求 需要 请求两次才能执行下一步操作的话 就需要用到
all
方法
这里不过多介绍 大家了解就行
https://www.jianshu.com/p/7e60fc1be1b2
<script>
new Promise.all([
new Promise((reslove, reject) => {
setTimeout(() => {
reslove('result1')
}, 2000)
}),
new Promise((reslove, reject) => {
setTimeout(() => {
reslove('result2')
}, 1000)
})
]).then(results => {
console.log(results); // 输出为一个数组
})
</script>
下面才是我们的主题
认识Vuex
什么是Vuex
官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- 它采用
集中式存储管理
应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。- Vuex 也集成到 Vue 的官方调试工具
devtools extension
,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理到底是什么
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
- 其实,你可以简单的将其看成把需要多个组件共享的变量(状态)全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
那为什么官方还要专门出一个插件
Vuex
呢?难道我们自己不能封装一个对象来管理呢?
- 这当然可以,只是我们要先想想
Vue.JS
带给我们最大的便利是什么呢?没错,就是数据响应式。- 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是
自己封装可能稍微麻烦一些
。- 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
管理什么状态(变量)?
但是,有什么状态时需要我们在多个组件间共享的呢?
- 但是,有什么状态时需要我们在多个组件间共享的呢?
- 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
- 比如用户的登录状态、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品等等。
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,
- 而且它们还是响应式的。
从理论上理解了状态管理之后,让我们从实际的代码再来看看状态管理。
毕竟,
Talk is cheap, Show me the code.
(来自Linus)
先来看看单界面的状态管理
单界面的状态管理
这图片中的三个东西,怎么理解呢?
State
: 状态,可以当做data中的属性View
: 视图层,可以针对State
的变化,显示不同的信息。Actions
: 用户的各种操作:点击,输入等,会导致状态的改变
单界面状态管理的实现、
首先我们来看下这个案例
在这个案例中,有一个状态需要我们管理counter
counter
需要某种方式被记录下来,也就是我们的State
counter
目前的值需要被显示在界面中,也就是我们的View 部分
界面发生某些操作时(这里是点击事件),需要去更新状态,也就是我们的Actions
这样下来就是我们上面的流程图!
多界面状态管理
Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
- 多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
- 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)
也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个视图共同想要维护的
- 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
- 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
- 没错,Vuex就是为我们提供这个大管家的工具。
全局单例模式(
大管家
)
- 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
- 之后,你们每个视图,按照我规定好的规定,进行访问和修改等操作。
- 这就是Vuex背后的基本思想。
Vuex 状态管理图例
来看下官方给的图
Vuex 基本使用
简单案例
我们还是实现一下前面的案例
官方文档 推荐一起看
首先我们先使用
npm
安装
npm install vuex --save
然后在src文件夹下创建一个文件夹 store,并且创建一个index.js文件
并且在刚刚创建的index.js 中写入下面的代码
import Vuex from 'vuex'
import Vue from 'vue'
// 安装 vuex
Vue.use(Vuex)
const store = new Vuex.store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
}
})
export default store
挂载到Vue实例中
其次,让我们所有的组件都可以使用这个store对象
- 来到
main.js
文件,导入store
对象,并且放在new Vue
中
这样,在其他Vue组件中,我们就可以通过
this.$store
的方式,获取到这个store对象了
使用Vuex 的count
在
App.vue
写入以下代码
<template>
<div id="app">
<p>{{count}}</p>
<button @click="inc">+1</button>
<button @click="dec">-1</button>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
computed:{
count: function() {
return this.$store.state.count
}
},
methods:{
inc:function(){
this.$store.commit('increment')
},
dec: function() {
this.$store.commit('decrement')
}
}
}
</script>
<style>
</style>
ok,这就是使用Vuex 最简单的方式了
我们对使用步骤,做一个简单的小节:
- 提取出一个公共的store对象,用于保存在多个组件中共享的状态
- 将store对象放置在
new Vue
对象中,这样可以保证所有的组件都可以使用到
3.在其他组件中使用store对象中保存的状态(count变量
)即可
- 通过
this.$store.state.属性
的方法来访问状态- 通过
this.$store.commit('mutations中的方法')
来修改状态(count的值)
注意事项:
- 我们通过提交
Mutation
(见状态管理图)的方式,而非直接改变store.state.count
- 这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变
store.state.count
的值
Vuex 核心概念
Vuex 有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
State 单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?
英文名称是Single Source of Truth(SSOT),也可以翻译成单一数据源。
比如说
- 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
- 所以Vuex也使用了单一状态树来管理应用层级的全部状态。
- 单一状态树能够让我们最直接的方式找到
某个状态的片段
,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
- 在上面的案例中我们已经使用过
State
,所以如何使用就不讲了
Getters 基本使用
有点像计算属性 computed
有时候,我们需要从store中获取一些state编译后的状态,比如下面的:
获取年龄大于20的个数
我们可以在store 中定义
getters
让我们对比一下在computed
中的写法
getters作为参数和传递参数
如果我们已经有了获取所有年龄大于20岁的学生列表(
greaterAgesStus()
),那么我们可以将代码写成这样:
这里我们通过greaterAgesStus()
获取学生的列表,然后在greaterAgesCount()
传入参数getters
在函数中通过getters.greaterAgesStus
得到学生的个数
getters
默认是不能传递参数的,如果希望传递参数,那么只能让getters
本身返回另一个函数。
比如下面的案例中,我们希望根据ID获取用户的信息
Mutation
状态更新
Vuex的store状态更新唯一方式:提交Mutation
Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
定义方式
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
通过mutation更新 (写在 App.vue 中)
methons:{
increment :funtion(){
this.$store.commit('increment')
}
}
传递参数
通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数
参数被称为是
mutation 的
载荷(payload)
(下面2的位置)
//Mutation中的代码:
mutations: {
increment (state, n) {
state.count -= n
}
}
//APP.vue 中的代码
methons:{
increment:function (){
//2 也可以写成一个对象 并传入
this.$store.commit('decrement',2)
}
}
但是如果参数不是一个呢?
- 比如我们有很多参数需要传递
- 这个时候,我们通常会以对象的形式传递,也就是
payload
是一个对象- 这个时候可以再 从对象中取出相关的信息
//APP.vue 中传入对象 count
methons:{
increment:function (){
this.$store.commit('decrement',{count:2})
}
}
//Mutation中的代码:
mutations: {
increment (state, payload) {
state.count = payload.count
}
}
提交风格
上面的通过了
commit
进行提交是一种普通的方式
Vue还提供了另一种风格,它是一个包含type属性的对象
//APP.vue 中传入对象 count
methons:{
increment:function (){
this.$store.commit({
type:'increment', // 函数名称
count:2
})
}
}
Mutation中的处理方式是将整个
commit的对象
作为payload使用, 所以代码没有改变, 依然如下:
//Mutation中的代码:
mutations: {
increment (state, payload) {
state.count = payload.count
}
}
Mutation 需遵守 Vue 的响应规则
Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新
这就要求我们必须遵守一些Vuex对应的规则:
- 提前在 store中初始化所需的属性。
- 当给state中的对象添加新属性时,使用下面的方式:
方式一: 使用Vue.set(obj,‘newProp’,123)
方式二:用新对象给旧对象重新赋值
下面来看下一个例子:
- 当我们点击更新信息时,界面没有发生对应改变。
如何才能让它发生改变呢?
- 查看下面代码的方式一和方式二
- 都可以让state中的属性是响应式的
删除 -这个方法不是响应式的
上面是添加属性,那怎么删除属性呢
//delete state.info.age 这个方法不是响应式的
Vue.delete(state.info, 'age')
//删除了 age 这个属性
常量类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
我们可以创建一个文件:
mutation-types.js
, 并且在其中定义我们的常量.
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
下面来看代码:
- 首先我们在store 文件下创建一个
mutation-types.js
,并写入以下代码
2. 然后我们在store 文件下的index.js
文件中导入上面的文件,并且将函数名进行替换
3. 在App.vue
中同时引入 ,同时也是将进行替换
同步函数
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法
- 主要的原因是当我们当使用devtools(vue 插件 )时,可以帮助我们捕捉mutation的快照
- 但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成
比如我们之前的代码,当执行更新时,devtools 会有下面的信息:
但是,如果Vuex 中的代码,我们使用了异步操作:
你会发现state中的info数据一直没有改变,因为它无法追踪到
所以,通常情况下,不要在mutation
中进行异步操作
Action
定义
我们在上面强调,不要在
mutation
中进行异步操作
- 但是某些情况,比如网络请求,必然是异步的,这个时候怎么处理呢?
- Action 类似于Mutation ,但是用来替代Mutation进行异步操作。
Action 的基本使用代码如下:
context 是什么
context是和store
对象具有相同方法和属性的对象.- 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
- 注意,但它们并不是同一个对象
那这样的代码是否多次一举?
- 我们定义了actions ,然后在actions 中去进行commit,这不是脱裤子放屁吗?
- 事实上并不是这样,如果在Vuex中有异步操作,那么我们就可以在actives中完成了
分发 Action 在组件调用
在Vue组件中, 如果我们调用action中的方法, 那么就需要使用
dispatch
同样,也是支持传递payload
Action返回的Promise
前面学习ES6语法中说过,Promise 经常用于异步操作
- Action中, 我们可以将
异步操作放在一个Promise
中, 并且在成功或者失败后,调用对应
的resolve或reject
.
我们来看下面的代码
Module
认识
Module 是模块的的意思,为什么在Vuxe中我们要使用模块呢?
- Vue使用单一状态树,那么意味着很多状态都会交给Vuex来管理
- 当应用变得非常复杂时,store对象就有可能变得相当臃肿。
- 为了解决这个问题,Vuex允许我们将store分割成模块(Modules),而每个模块都有自己的
state ,mutations、actions、getters
等
我们按照什么样的方式来组织模块呢?
局部状态
上面的代码中,我们已经有了整体的组织结构,下面我们来看看具体的代码如何书写。
- 我们在
moduleA
中添加state ,mutations、getters
- mutation和getters接收的第一个参数是局部状态对象
- 方便看
注意
- 虽然,我们的
doubleCount
和increment
都是定义在对象内部的,- 但是调用的时候,依然是通过
this.$store
来直接调用的。
Action的写法
actions的写法呢? 接收一个context参数对象
- 局部状态通过
context.state
暴露出来,根节点状态则为context.rootState
- 这里使用了对象的解构
如果
getters
中也需要使用全局的状态, 可以接受更多的参数
项目结构
我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰.
-
展示
-
引入
import 类名 from '路径'
-
导出 通过
export defaull{}
-
141