VUE组件系统极速入门2--vuex
看完了官网vuex一头雾水的,比如vuex中的...mapState、countAlias: 'count'等。没看过官网也没事,希望对你们有帮助。
下文都是我的个人理解,官网的就引用了代码,也许存在着很多错误的描述或者理解,请各位请教,我会改正的。
这个坑之前就深挖了~现在是时候填了。
为什么要有vuex?
它是管理组件与组件通信的库。
子组件与父组件之间通信还是很方便的。但是如果是同级组件,或者是其它的复杂情况,比如:
- a.vue中的子组件想与b.vue中的子组件的通迅
- a.vue想与b.vue中的子组件的通迅
- a.vue中的子组件想与b.vue通迅
- a.vue的子组件的子组件想与b.vue的子组件通迅
- .....
就是为了解决了类似的问题~
我们把事件想简单点吧,如果你管理那么多组件(这个改变了那个的数据,那个改变了这个的数据,那个组件又多了点什么,这个又少了点什么),你会怎么做呢?就说想法,不用实现。
我的想法是:没有蛀牙 把整个应用的数据全部集中起来,谁想用作显示都无条件同意,而谁想改,改完了一定要通知所以用到这个数据显示的组件,还不是美滋滋?
vuex也是这个意思,它一共分为以下5个部分
- state 放数据的
- Getter 获取数据的
- Mutation 同步改变数据的
- Action 异步改变数据(利用Mutation实现)
- Module 可以把全局的数据仓库分为几个单独的模块,比如用户一个,文章一个。
1 state
1.1 state 定义
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
然后mutations先提一下怎么用吧(官网的例子,原汁原味 )
store.commit('increment')
console.log(store.state.count) // -> 1
然后要把这货放在组件里,如果放入父组件中,那么子组件也可以使用。通过 this.$store。
//普通的定义
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: {XXX},
template: `XXX`
})
//*.vue
export default {
el: '#app',
store,
components: {XXX}
}
</script>
1.2 关于mapState
mapState的作用说到底,就是两个作用,先说第一个:别名。
上官网的例子,真的是要了亲命了唉):
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
先说说mapState的作用,返回一个对象。
什么样的对象
{
cout: xxx //第一个
coutAlias:xxx //第二个
countPlusLocalState(state){xxxx}//第三个
}
我尽可能说详细一点,
export default {
computed: mapState({
/*根据箭头函数,生成的函数为
function(state){ return state.count},
所以count的值最后是state.count。count是个值。*/
count: state => state.count,
/* countAlias只是个名字,与上面的count没有一毛钱关系,我们的大前提,
是使用的vuex里的mapstate,所以,只要我们给一个store里的属性名,
mapstate就会把属性名对应的属性返回给我们,
并用我们提供的新名字。所以,countAliae就对应的state里的count。
*/
countAlias: 'count',
/*注意,我强调一下,如果你只给属性名,不给别名,那么就会按原属性名返回。 比如就是一个孤零零的 'a' 你调用的时候也要调a就完事了,上面的是调countAlias */
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
/* 箭头函数基础,亲,如果你看不懂为什么要这样,请去参考下ES6的箭头函数 */
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
接下来讲mapState的第二个作用:偷懒。
突然出现了一个...mapState,这个...就是对象展开运算符,是ES6里的,可关键是为什么搞了这么一个另人费解的东西,有什么用呢?
答:并没有什么卵用还是有点用的,比如,你的compute属性里想用store里返回的东西,一共用5个!就可以用mapstate传5个属性名进去把五个搞出来再添加到compute里。
比如
computed:{
a1:function(){xxxxx}
}
可是你需要a2到a6,如果一个一个的写的话,就会这样:
computed:{
a1:function(){xxxxx},
a2:function(){return this.$store.a2},
a3:function(){return this.$store.a3},
.......
}
有了这个...
//注意,我再强调一下,如果你只给属性名,不给别名,
那么就会按原属性名返回。所以可以这么写:
compute:{
a1:function(){xxxxx},
...mapState({'a2','a3','a4','a5','a6'})
}
然后就全有了,和上面的作用一样,看吧,偷懒就完事了。
2 Getter
首先要审明下,做为读书人的我,也是会骂人的!!!!
之前很多程序员总是有些很奇怪的爱好,比如他母亲的Getter。这个Getter一见到名字就是想起了被java支配的恐惧,所以不说了 所以一定要好好理解!
vuex里的getter,是在store里的一个东西,为什么需要它呢?比如,
this.$store.state.todos.filter(todo => todo.done).length
这一句分别在十个函数里都出现了,就可以把他考虑做成一个getter函数。
把官网的例子搞了过来
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getter 会暴露为 store.getters 对象:
store.getters.doneTodos // 运行会得到 [{ id: 1, text: '...', done: true }]
Getter接受其他 getter 作为第二个参数,很奇怪是吧,但是有时候需要用到getters里的别的函数。(这里定义时不支持this直接拿)
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // 得到1
在任何组件中(只要是上一级组件有store的)都可以使用:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
getter 为什么好好的要返回一个函数?因为你返回了一个函数,还可以给函数传值,让他查些什么玩意,难道你们没有发现上面的例子都只是没有传我们自定义的参数的?现在发现了就行~
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
还有剩下的mapGetters,嘿嘿,和上面的mapState一样,返回属性的规则也一样:1 别名 2 偷懒
3 Mutation
如果 1与2都是获取数据展示的话,那么3与4就是修改完通知了~
搞一手官网:
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。
一个最基本的例子:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
//使用:
store.commit('increment')
//注意,这里的increment 只能通过store.commit('increment')才能触发
载荷,传事件的时候同时传个参数
既然能传一个字符串的事件类型的increment,那么一定会有别的更复杂的使用方法。妥妥的,commit里还能搞个别的东西传过去,这个就叫:载荷(payload)(我觉得可以理解成: 装备)
mutations: {
increment (state, payload) {
state.count += payload.amount
}
//提交的风格有两种(还是从官网上搞的)
//风格1:
store.commit('increment', {
amount: 10
})
//风格2:(个人觉得这个逼格更高一点点)
store.commit({
type: 'increment',
amount: 10
})
使用payload也是有条件的,
- 提前在你的 store 中初始化好所有所需属性
- 在对象上添加新属性时,应该更新这个属性,而不是再用一个新的对象
- 使用
Vue.set(obj, 'newProp', 123)
- 以新对象替换老对象。
state.obj = { ...state.obj, newProp: 123 }
- 使用
使用更短的名字使用Mutation&在组件中使用Mutation
你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。 (官网原话)
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', /*将 `this.increment()` 映射为
`this.$store.commit('increment')`*/
// `mapMutations` 也支持载荷:
'incrementBy' /*将 `this.incrementBy(amount)` 映射为
`this.$store.commit('incrementBy', amount)`*/
]),
...mapMutations({
add: 'increment' /* 将 `this.add()` 映射为
`this.$store.commit('increment')`*/
})
}
}
然后官网也提到了使用mutations的注意事项
不能使用异步函数(等待时间不能太长的函数,什么网络连接,读写文件,都不是异步函数)
如果你的程序中,有很多'increment'这种事件,推荐用常量保存后放在一个文件中,就像这样:
//文件名: mutation-types.js
export const GO_TO_PICS = 'GO_TO_PICS'
export const GO_TO_NEWS = 'GO_TO_NEWS'
export const GO_TO_WE = 'GO_TO_WE'
4 Action
离4这个标题不远处,你依稀可以看到,mutations不能使用异步函数,
Action就是提交异步事件的~
Action 提交的是 mutation,Action提交的是Action ,提交的是 mutation。重要的事件要说三遍。所以,Action是在mutation外又加了一层,看一个简单的例子。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
context是什么玩意?有什么用?
答:他是和 store 实例具有相同方法和属性的 context 对象。作用是[个人猜测,未验证]context对象作为store的辅助,也许是store的一些操作一定要通过context进行。
调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
少写一点(引用自网站,纯语法):
实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用
commit
很多次的时候):
actions: {
//这是ES6的语法
/*举个例子:{ blowUp } = { blowUp: 10 }; 结果:{blowUp:10}
从blowUp中取一个叫blowUp的属性。
参数本来是传过来的context,从context中取一个commit的东西,得到 { commit : context.commit };
挺另人头大的。
*/
increment ({ commit }) {
commit('increment')
}
}
使用action
store.dispatch('increment')
但是最主要是异步调用:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
同样的支持载荷(payload,装备)
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
官网上的一个更复杂的例子-购物车:
actions: {
// es6的语法,从context中取commit与state
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来 ...对象展开
const savedCartItems = [...state.cart.added]
// 发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
上面只是定义,还有在组件中使用action的方法,不要忘记这个。
关于mapActions,作用和定义你们懂得,下面用的官网的例子:
你在组件中使用
this.$store.dispatch('xxx')
分发 action,或者使用mapActions
辅助函数将组件的 methods 映射为store.dispatch
调用(需要先在根节点注入store
):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', /* 将 `this.increment()` 映射为
`this.$store.dispatch('increment')`*/
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
组合 Action(全盘搬的官网,这个部分很好理解)
Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?
首先,你需要明白 store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
现在你可以:
store.dispatch('actionA').then(() => {
// ...
})
在另外一个 action 中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
最后,如果我们利用 async / await,我们可以如下组合 action:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
一个
store.dispatch
在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
5 Module
这个部分以后再说吧,想了解的可以去官网看看先。