Vuejs(四):Vuex
一:promise
Promise到底是做什么的呢?Promise是异步编程的一种解决方案。
那什么时候我们会来处理异步事件呢?一种很常见的场景应该就是网络请求了。我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。但是,当网络请求非常复杂时,就会出现回调地狱。
# 链式调用
<script>
// Promise要求传入的参数是一个函数(resolve,reject),resolve,reject本身又是函数
new Promise((resolve, reject) => {
// 假设:这里是第一次网络请求
setTimeout(() => {
// 只要调用resolve()会走到下面的then()中
resolve()
},1000)
}).then(() => {
//第一次网络请求结果进行处理
console.log('Hello promise');
console.log('Hello promise');
return new Promise((resolve,reject) => {
// 第二次网络请求
setTimeout(()=> {
resolve()
},1000)
})
}).then(() => {
// 第二次网请求结果处理
console.log('Hello java');
console.log('Hello java');
})
</script>
1.1 Promise三种状态
首先, 当我们开发中有异步操作时, 就可以给异步操作包装一个Promise,异步操作之后会有三种状态
- pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
- fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
- reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
new Promise((resolve,reject) => {
setTimeout(() => {
// resolve('Hello world') # 处理成功请求 -> then()
reject('error data') # 处理错误请求 -> catch()
}, 1000)
}).then(data => {
console.log(data);
}).catch(data => {
console.log(data);
})
简写方式:在then()函数中传入两个函数,一个函数处理成功数据,另一个函数处理失败数据;那么.catch()就可以省略
new Promise((resolve,reject) => {
setTimeout(() => {
// resolve('Hello world')
reject('error data')
}, 1000)
}).then(data => {}, error => {})
1.2 Promise all方法
假如某一个网络请求依赖于另外两个网络请求,必须等待另外两个网络请求完成
Promise.all([
new Promise((resolve,reject) => {
// 第一个网络请求
}),
new Promise((resulve,reject) => {
// 第二个网络请求
})
// Promise内部会判断上面两个请求有没有都完成,如果完成则进入到then中
]).then(results => {
// 这里得到的results为一个数组,分别为第一个网络请求的结果与第二个网络请求的结果
results[0]
results[1]
})
二:Vuex
2.1 Vuex是什么
官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理到底是什么?
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
- 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
- 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
总结:Vuex就是为了提供这样一个在多个组件间共享状态的插件
但是,有什么状态时需要我们在多个组件间共享的呢?如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。比如用户的登录状态、用户名称、头像、地理位置信息等等。比如商品的收藏、购物车中的物品等等。这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的。
2.2 单界面的状态管理
在单个组件中进行状态管理是一件非常简单的事情
State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)
View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)
Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
<template>
<div id="app">
<h2>{{message}}</h2>
<h2>{{counter}}</h2>
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
message: 'APP Component',
counter: 0
}
},
}
</script>
2.3 多界面状态管理
npm install vuex --save # 安装vuex
在src目录下新建store/index.js文件,内容如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
# 下面的五个对象是固定的,是Vuex中的五个核心概念
state: { # state保存数据的地方
counter: 1000
},
mutations: {},
actions: {},
getters: {},
modules: {},
})
export default store
main.js文件中挂载store对象
import store from './store'
Vue.config.productionTip = false
new Vue({
el: '#app',
store,
render: h => h(App)
})
由于上面state中设置了counter的值为1000,因此在任何组件中都可以访问counter变量,访问方法如下:
<h2>{{$store.state.counter}}</h2>
可以访问到counter变量,那么组件中如何修改counter变量呢?官方不建议使用下面的方法来修改(尽管它确实可以直接修改counter变量):
<button @click="$store.state.counter--">-</button>
而是要按照一定的规则去修改state中的变量:
Devtools是官方开发的浏览器插件,它可以记录谁修改了state中的变量
Actions是做异步操作的地方,操作完后将数据提交到Mutations中,这样Devtools才能跟踪到数据,因为Devtools只能跟踪到同步数据,不能跟踪异步数据。
mutations: {
// 定义方法
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
在其他组件中修改方法如下:
<template>
<div id="app">
.......................
<button @click="addition">+</button>
<button @click="subtraction">-</button>
.......................
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld'
export default {
name: 'App',
data() {
return {
message: 'APP Component',
}
},
methods: {
addition() {
this.$store.commit('increment') # 参数为mutations定义的对应的方法
},
subtraction() {
this.$store.commit('decrement')
}
},
components: {
HelloWorld
}
}
</script>
2.4 getters
有时候,我们需要从store中获取一些state变异后的状态
如:上面在state中定义了counter为1000,如果所有的组件都希望获取这个值的平方,那么此时就可以在getters中定义一个方法
getters: {
powerCounter(state) {
return state.counter * state.counter
}
},
组件中获取这个值的方法:
<h2>{{$store.getters.powerCounter}}</h2>
2.5 mutations
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
通常情况下, 不要再mutation中进行异步的操作
mutations传参
mutations: {
incrementCount(state, count) {
state.counter += count
}
},
methods: {
addCount(count) {
this.$store.commit('incrementCount', count)
}
},
Mutation提交风格
上面的通过commit进行提交是一种普通的方式
Vue还提供了另外一种风格, 它是一个包含type属性的对象
methods: {
addCount(count) {
// this.$store.commit('incrementCount', count)
this.$store.commit({
type: 'incrementCount',
count: count
})
}
},
mutations: {
incrementCount(state, count) {
console.log(count); # 如果是以type的方式提交过来那么这里的count就是一个对象
}
},
Mutation响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新
这就要求我们必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性.
- 当给state中的对象添加新属性时, 使用下面的方式:
方式一: 使用Vue.set(obj, ‘newProp’, 123)
方式二: 用新对象给旧对象重新赋值
const store = new Vuex.Store({
state: {
info: {name: 'Andy',age: 17,height: 1.78}
},
mutations: {
updateInfo(state) {
// state.info['address'] = 'Los' # 不是响应式
// Vue.set(state.info, 'address', 'Los') # 增数据响应式
// delete state.info.age # 不是响应式
Vue.delete(state.info, 'age') # 删除响应式
}
},
actions: {},
getters: {
},
modules: {
},
})
Mutation常量类型
我们来考虑下面的问题:
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称)。当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多。方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况。
如何避免上述的问题呢?
在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型。我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
具体怎么做呢?
我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量。定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称。
在store目录下新建mutation-types.js文件,内容如下:
export const UPDATEINFO = 'updateInfo'
store/index.js文件中导入上面的常量:
import Vue from 'vue'
import Vuex from 'vuex'
import {UPDATEINFO} from './mutations-types'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
info: {name: 'Andy',age: 17,height: 1.78}
},
mutations: {
[UPDATEINFO](state) {
Vue.set(state.info, 'address', 'Los')
}
},
actions: {},
getters: {
},
modules: {
},
})
在组件中的调用方法如下:
<script>
import HelloWorld from './components/HelloWorld'
import {UPDATEINFO} from './store/mutations-types'
export default {
name: 'App',
data() {
return {
message: 'APP Component',
}
},
methods: {
updateInfo() {
this.$store.commit(UPDATEINFO)
}
},
components: {
HelloWorld
}
}
</script>
2.6 Actions
我们强调, 不要再Mutation中进行异步操作。但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的。这个时候怎么处理呢? Action类似于Mutation, 但是是用来代替Mutation进行异步操作的。
const store = new Vuex.Store({
state: {
info: {name: 'Andy',age: 17,height: 1.78}
},
mutations: {
updateInfo (state) {
Vue.set(state.info, 'address', 'Los')
}
},
actions: {
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => { # 这里返回一个Promise
setTimeout(() => {
context.commit('updateInfo') # 操作mutations
console.log(payload);
resolve('1111')
}, 1000)
})
}
},
getters: {
},
modules: {
},
})
组件中调用actions:
methods: {
updateInfo() {
this.$store.dispatch('aUpdateInfo', '携带的参数').then(res => { # 注意.then是在这里调用了
console.log('里面完成了提交');
console.log(res);
})
}
},