为什么要用Vuex?
- 如果只是父对子数据交互,那么应该考虑使用
props
进行单向传递; - 如果涉及到子组件向父组件的数据传递,那么应该考虑使用
$emit
和$on
。 - 涉及到非父子关系的组件,例如兄弟关系、祖孙关系,甚至更远的关系,他们之间如果有数据交互,那么应该使用Vuex来实现。
怎么使用?
准备Vuex实例
- 安装vuex:
npm install vuex --save
。 - 准备vuex实例:
export default new Vuex.Store({})
,传入的是一个对象,其中的属性有state, getters, actions, mutations, modules。
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
- 在
main.js
中注册vuex实例
new Vue({
router,
// 这可以把 store 的实例注入所有的子组件
store,
render: h => h(App)
}).$mount('#app')
state
在vuex实例中声明了一个state变量:persons,用来存储home.vue和about.vue这两个组件都需要用到的用户状态。
state: {
persons: [{
name: "张三",
type: "VIP",
rest: 100
}, {
name: "李四",
type: "normal",
rest: 0,
}, {
name: "王五",
type: "VIP",
rest: 0,
}]
},
则在home.vue中用this.$store.state.persons
,来得到这些用户状态。
getters
就像计算属性一样,可以对state进行进一步处理,当然这个处理最好是对于多个组件有意义。如果是针对于某个组件有意义,则完全可以写到该组件的computed中。
比如home.vue和about.vue都需要使用VIP客户信息,则可以在getters中筛选。
getters: {
// vipShow(state, getters), 通过state参数获得状态,通过getters参数获得其他处理状态。
vipShow(state) {
return state.persons.filter(p => p.type == "VIP")
}
},
在使用到该状态的组件中,用this.$store.getters.vipShow
来得到处理后的用户状态。
如果需要筛选出VIP剩余天数大于t天的用户,即this.$store.getters.vipShow(t)
的使用场景:有参数传递的情况下,getters该如何处理?
这也是最妙的一个地方,这个参数并没有传递到getters中的vipShow参数中,而是通过返回一个函数 来实现的。
getters: {
vipShow2(state) {
return t => state.persons.filter(p => p.type == "VIP" && p.rest > t)
}
},
mutations
mutations类似于methods,用来定义一些对状态的操作。
mutations: {
// mutations中的函数同样接收一个state,也接收一个参数对象payload。
// 这个方法用来对每个用户VIP续费num天。
increment(state, payload) {
return state.persons.map(p => p.rest += payload.num)
}
},
但是与methods不同的是,mutations中的函数 只能通过commit触发,不能手动调用。
(组件)
<button @click="btnClickHandler">续费一年</button>
methods: {
btnClickHandler() {
this.$store.commit("increment", { num: 365 })
}
}
mutations的响应式
- 如果state中有个状态(如persons)是数组,需要修改元素值,直接修改,则无法响应到View中。
// Model层改了,但View没改。 // state.persons[0] = { // name: "赵七", // type: "normal", // rest: 0 // } Vue.set(state.persons, 0, { name: "赵七", type: "normal", rest: 0 })
- 如果state中有个状态(如info)是对象,需要增加属性,使用
Vue.set(obj, key, value)
。info: { name: 'kobe', age: 18 } ----------------------------- // Model层改了,但View没改。 state.info.height = "1.88"; // 方式一: Vue.set(state.info, "height", "1.88") // 方式二: state.info = { ...state.info, "height": "1.88" }
actions
实际上,mutations也可以包含异步函数,程序能够正常运行。不过因为在devtools中无法追踪,因此建议把异步函数放到actions中。
看下面一个例子:
假如倒计时3s后,将所有用户提升为VIP。
actions: {
// context是一个对象, 与store实例相同方法和属性。context.state, context.getters, context.commit(),...
setAllVIP(context) {
setTimeout(() => {
context.state.persons.map(p => p.type = "VIP")
}, 3000);
}
},
然后在组件中this.$store.dispatch("setAllVIP")
,就能提升所有用户为VIP了。虽然这样写确实可以达到效果,但是并不是官网推荐(如下图所示)的做法,而且这么做同样不能使得devtools能够追踪到变化。
因此,按照上图的做法,应该这么写:
mutations:{
setVIP(state) {
state.persons.map(p => p.type = "VIP")
}
},
actions: {
setAllVIP(context) {
setTimeout(() => {
context.commit("setVIP")
}, 3000);
}
},
组件中写法不变:this.$store.dispatch("setAllVIP")
,值得一提的是,dispatch
函数返回的是setAllVIP
函数的返回值。
因此如果希望异步操作完成后,能够打印一下提示信息,可以将setAllVIP
函数中的异步操作包裹在一个Promise中,返回。
actions: {
setAllVIP(context) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit("setVIP")
resolve("升级成功了");
}, 3000);
})
}
},
(组件)
this.$store.dispatch("setAllVIP").then(res => {
console.log(res); // 升级成功了
})
module
- module中的模块会被当做一个状态放到全局state中。因此使用时
$store.state.moduleA
。 - 模块内部的 action、mutation 和 getter 是注册在全局命名空间的。