一. vuex简介
1. Vuex是做什么的? (数据/变量大管家)
Vuex是一个专门为Vue.js应用程序开发的状态管理模式
- 它采用集中式存储管理应用的所有组件状态,保证状态以一种可预测的方式发生变化.
- 可以简单看成: 把需要多个组件共享的变量全部存储在一个对象里
- 然后放在顶层的Vue实例中,让其他组件使用.
- 从而,多个组件可以共享对象中的变量和属性了
- vuex是响应式的!!!
2. 什么样的东西会放在vuex里?
- 多个组件需要共享的
- 数据传递层级跨越较多的
- 如果只是个父子关系的数据传递,没必要,直接用父子通讯状态
下面列举几个常用的需要共享的状态
- 用户登录状态\用户名称\头像\地理位置
- 商品收藏,购物车中的物品等等
- 服务器发给我们的token
二. 如何使用vuex
1. 安装vuex
cnpm install vuex --save
2. 创建vuex
- src目录新建文件夹 store用来储存vuex的代码
- 新建文index.js
- 在里面引入Vue和Vuex
- 使用Vuex插件
Vue.use(Vuex)
- 创建store对象
const store = new Vuex.store({})
- 导出store对象
export default store
- 在main.js里挂载 store对象
import store from './store'
- 把store对象赋值给store属性
一旦完成这些步骤,vue里的所有组件都多了一个@store
对象
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
// ...
})
export default store
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
el:"#app",
store,
render: h => h(App),
}).$mount('#app')
3. store里面的对象里放什么?
store里比较核心的几个概念:
- state 保存状态的地方, 单一状态处理
- getters 类似于计算属性
- mutations 状态更新 官方唯一提交数据通道
- actions
(1) state
Vuex提出单一状态树的概念,也可以翻译成单一数据源,
我们创建的store对象是一个单例,不能重复声明
//我们在store中定义:
state: {
counter: 0
},
在其他组件中使用时
<h3>{{$store.state.counter}}</h3>
值得注意的是:我们想要修改数据时, $store.state.counter,
而是要通过Mutations按照官方规定的步骤改写.详见下文
(2) getters
getters相当于VUE实例里面的计算属性,
getters里面需要写方法,同时,方法中会默认传入第一个参数state
例如: 我们在store中定义:
getters:{
//定义方法
powerCounter(state){ //默认传入了state
return state.counter*state.counter
}
},
在其他组件中使用时
<h2>{{$store.getters.powerCounter}}</h2>
此外,除了第一个默认参数state 还有第二个默认参数getters
如果在某个getter属性中想用其他getter属性中的内容,可以通过此getters对象获得
例如:
getters:{
powerCounter(state){
return state.counter*state.counter
},
powerCounterMoreThan100(state,getters){
return getters.powerCounter>100
//这个getter属性中使用了上一个getter属性
}
},
//调用时:
<h2>{{$store.getters.powerCounterMoreThan100}}</h2>
getters是不能传参的, 已经有两个默认参数了
如果想往里面传入参数,只能向外面传出函数,函数中定义形参
getters:{
powerCounter(state){
return state.counter*state.counter
},
powerCounterMoreThan100(state,getters){
return getters.powerCounter>100
},
powerCounterMoreThanN(state,getters){
return function (n) { //这个getter属性传出了一个函数,这个函数定义形参来接收变量
return getters.powerCounter>n
}
}
},
//使用时:
<h2>{{$store.getters.powerCounterMoreThanN(50)}}</h2>
(3) Mutations状态更新
Mutations是官方唯一提交数据通道
前面我们说到:不要随便改写state中的数据,如果需要更新状态,
请按照官方的方法使用
根据上图,如果想修改数据,应该
- 发布一个事件到action (如果有异步操作在此处理,如果没有可以跳过此步)
- 提交一个mutation 改变请求
- mutation 改变state里的数据
这样做devtools能监测到数据的改变.
①. 如何定义一个mutations??
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0
},
mutations: {
//定义方法
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
actions: {
},
modules: {
}
})
这里我们注意,mutations里传入的其实是键值对,只是简写了
increment : increment(state) {
state.counter++
},
我们把 " 键 " 称之为:事件类型(type) ,
把 " 值 " 称之为:回调函数(handler) 该回调的第一个参数是state
②. 如何使用一个mutations
我们在触发事件中发射事件的type即可
methods: {
btnClick(){
this.$store.commit("decrement") //传入事件类型
}
},
③ mutations里如何传参?
直接传即可
比如,我们给mutations里的decrement类型事件传入一个参数n,让它每次执行都减n
mutations: {
//直接在默认传的state后面写入想传入的参数即可
decrement(state, num) {
state.counter -= num
}
},
调用时:
methods: {
btnClick(){
this.$store.commit("decrement",5)
}
},
参数被称为mutation的载荷(payload) , payload可以是对象
④ mutation的提交风格
普通提交风格
this.$store.commit("事件名",payload)
对象式提交风格
methods: {
btnClick(){
this.$store.commit({
//传入的是一个对象,这个对象会找到与自己的type同名的mutation,提交事件
//同时把自己作为第二个参数传入这个mutation中
type:"decrement",
num:5
})
}
},
我们可以在被提交的mutation中接受这个对象
decrement(state, payload) {
console.log(payload);
state.counter -=payload.num
}
⑤. mutations的响应方式
之有预先加入响应式系统的对象和参数才会被响应
比如:
state: {
stu:{
name:'lili',
age:20,
gender:"girl"
}
},
这时候,我们修改name 或 age都会被正确的响应
但是,如果我们给这个对象增加一个属性,比如:
mutations: {
addAddress(state){
state.stu['address']='losAngeles'
},
}
这种属性是后加的 不会有响应式效果
如何解决?
如果是数组还是要用数组的响应式方法 见:vue.js备忘记录(四)
如果是对象:
Vue.set(state.stu,'address','losAngeles')
⑥ 那么如何删除对象的属性?
Vue.delete(state.stu,'address')
⑦ mutation的类型常量
我们可以把mutations里的事件类型都定义到一个文件里,然后store/index.js 和 APP.vue组件中都引用这个文件,就可以像引用常量一样引用事件类型了,而且在VSCODE中有代码提示啦😘
- 第一步:在store文件夹下新建mutations-types.js
- 第二步:在mutations-types.js中新建常量
export const INCREMENT ='increment'
- 第三步:在 store/index.js中声明时:
import {INCREMENT} from './mutations-types'
...
...
mutations: {
[INCREMENT](state) {
state.counter++
},
}
- 第四步:在组件中应用时:
import {INCREMENT} from './store/mutations-types'
...
...
methods: {
add(){
this.$store.commit(INCREMENT)
},
}
这才是官方推荐的方式
⑧ mutations中的方法必须是同步的
主要原因是,如果在mutations中放入异步操作,devtools是不能捕捉的
那确实需要异步操作时怎么办?
使用Action替代Mutations
(4) actions //状态 数据更新前的 处理异步
如果一个mutation需要提前进行异步处理,则,我们需要把异步部分写在actions中,异步执行结束后要提交对应的事件类型事件,再有mutations接管
①. 如何定义actions ?
mutations: {
updateInfo(){
state.counter=0
},
},
actions: {
aUpdateInfo(context){//context上下文
setTimeout(()=>{
context.commit('updateInfo')
},1000)
}
},
②. 如何使用? $store.dispatch
btnClick(){
this.$store.dispatch('aUpdateInfo')
}
③. 如何传参? payload
和mutations一样的方法传参
④. 异步执行完毕如何告知调用者?
可以使用 '对象式dispatch' 和mutations的对象式提交很像
我们在dispatch时,还可以传入一个对象
btnClick(){
this.$store.dispatch('aUpdateInfo',{
message:'我是个传入action的参数',
success:()=>{
console.log('异步已经执行完了,并告诉我了');
}
})
actions: {
aUpdateInfo(context, payload) {//上下文
setTimeout(() => {
console.log(payload.message);
payload.success()
context.commit('updateInfo')
}, 1000)
}
},
更优雅的写法:
btnClick() {
this.$store
.dispatch("aUpdateInfo", "我是个传入action的参数")
.then(res=>console.log(res));
}
//------------------------------------------
actions: {
aUpdateInfo(context, payload) {//上下文
return new Promise((solved) => {
setTimeout(() => {
console.log(payload);
context.commit('updateInfo')
solved("我是传出数据")
}, 1000)
})
}
},
(5) modules
前面所有的内容放在一个store对象里,实在太臃肿了,所以vuex提供了modules,让我们把数据分离, 每个module都可以有自己的state mutations actions getters等等
①. 模块中的state怎么用
调用模块中的数据时, store.state.a.name
②. 模块中的mutations怎么用
$store.commit
并不关心是否在模块中
发射事件,$store.commit(事件类型)
这里需要注意:事件类型没有定义是在哪个模块里,所以要确保事件类型不要重名
③. 模块中的getters怎么用
$store.getters
并不关心是否在模块中
使用时可以:$store.getters.参数名
所以也要注意重名 问题
直接store 不在modules里的state 称为 rootState
getter也可以调用根目录里的数据
const moduleA = {
state:{name:'lili'},
getters:{
fullname1(state){
return state.name+' '
},
fullname2(state,getters,rootState){
return getters.fullname1.name+rootState.name
}
}
}
④. 模块中的action怎么用
const moduleA = {
state: {
name: 'zhangsan'
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
actions: {
aUpdateName(context) {
setTimeout(() => {
console.log(context);
context.commit('updateName', 'lisi')
}, 1000);
}
}
}
//----------------------------------------------------
methods: {
testBtn(){
this.$store.dispatch('aUpdateName')
}
},
这里我们打印context,发现context本身就有了rootGetters和rootState. 直接可以用了.
action的另一种写法:(解构context)
context={state,commit,rootState}
所以在函数内部 state就是context.state
三. store文件夹目录结构组织
我们把除了state之外的每个属性都做成独立的文件,并建立modules文件夹专门管理模块
每个文件都用
exprot default {具体内容}
暴露出来.在index.js导入
最终index.js的样子是:
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const state = {
counter: 0
}
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
a: moduleA
}
})
export default store