Vuex复盘

136 阅读5分钟

这篇文章主要收录了Vuex的基本使用和它的5大核心:state、getters、mutations、actions、modules

1 状态管理

vuex是什么?

状态管理工具

状态管理是什么?

可以简单看成多个组件需要共享的变量全部存储到一个对象里面

为什么官方还要专门出一个vuex插件?

能共享、响应式

管理什么状态呢?

  • 用户的登陆状态
  • 用户名称、头像、位置等等
  • 购物车等等

1.1 基本使用

多个组件之间怎么共享状态的呢?

image-20211127100038263.png

Vue Compents就是组件树

需要进行共享和管理的状态放State对象里面;

当需要修改某个状态时,commit一个mutation(不要直接改!),就会执行Mutations中对应的函数,修改状态;

Mutations中是不允许有异步请求的;

当修改某个状态需要发起异步请求时,就需要dispatchActions,然后由Actions去commit到Mutations;

创建store

vuex3.x的写法

安装

npm install vuex -s

store文件夹下的index.js

使用Vuex.Store() 创建,state是个对象;

import vuex from 'vuex'

const store = new vuex.Store({
  state: {
      counter
  }
})

export default store

vuex4.x写法

安装

npm install vuex@next -S

store文件夹下的index.js

使用createStore() 创建,state是个函数,类似vue2.x中的data选项;

import { createStore } from 'vuex'

const store = createStore({
  state() {
      return {
          counter: 0
      }
  }
})
    
export default store

main.js

vue2.x写法

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.use(store)

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

vue3.x写法

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

const app = createApp(App)
app.use(store)
app.mount('#app')

HelloWorld.vue

<h3>{{ $store.state.counter }}</h3>

提交修改

  • 直接改devtools跟踪不到是哪个组件修改了状态
  • 也可以从vue components直接到mutations,devtools只会跟踪同步操作
  • 是要是有异步操作(如网络请求)就不能绕过actions,actions会跟踪异步操作

store目录下的index.js;

mutations里面的方法会默认传入state参数;

import { createStore } from 'vuex'

const store = createStore({
  state() {
      return {
          counter: 0
      }
  },
  mutations: {
      increment(state) {
          state.counter
      }
  }
})
    
export default store

vue2.x写法

HelloWorld.vue

<h2>{{ $store.state.counter }}</h2>
<button @click="add()">+</button>
methods: {
  add () {
    this.$store.commit('increment')
  }
}
  • 使用commit() 提交
  • commit()的参数是在vuex实例中mutations的方法名

2 vuex核心

  • state
  • getters
  • mutations
  • action
  • module

2.1 state

单一状态树

一个对象包含全部应用的状态;

SSOT, Single Source Of Truth,单一数据源

为了方便管理和维护、模块化,vuex提供了module

辅助函数mapX()

vuex提供了一种辅助函数,方便获取状态,mapX(X可以是state、mutations等等);

如果想将某些状态放入计算属性,可以使用mapX;

mapX()返回的是一个对象

数组写法,如果想自定义名字可以使用对象写法

store目录下的index.js

import { createStore } from 'vuex'
const store = createStore({  
    state() {      
        return {          
            counter: 0,
            name: 'zsf'
        }  
    }
})
export default store

HelloWorld.vue

<h2>{{ counter }}</h2>
<h2>{{ name }}</h2>
import { mapState } from 'vuex'

computed: {
  // 其它计算属性省略
  ...mapState(['counter', 'name'])
}

2.2 getters

某些属性需要变化后才能使用,这个时候可以用getters

类似计算属性computed

getters里面的方法接收两个参数,第一个是state对象,第二个是其它getters对象

store文件夹下的index.js

import { createStore } from 'vuex'
const store = createStore({
    state() {      
        return {          
            counter: 0,
            name: 'zsf'
        }  
    },
    getters: {
        powerCounter(state) {
            return state.counter * state.counter
        }
    }
})
export default store

传参

如果希望getters里的函数传参呢?

getters: {
	moreAgeStu(age) {
        return function (age) {
            return state.student.filter((s) => s.age > age)
        }
    }
}

辅助函数

同样,getters也有对应的辅助函数mapGetters() ,用法与state的类似~;

2.3 mutations

传参

mutations里的函数的第二个参数payload,负载;

payload是通过 $store.commit()第二个参数传进来的;

Home.vue

<button @click="addCount(5)">+5</button>
methods: {
  addCount (count) {
    return this.$store.commit('increment', count)
  }
}

store目录下的index.js

mutations: {
    increment(state, payload) {
      state.counter += payload
    }
}

提交风格

一般风格

addCount (count) {
	return this.$store.commit('increment', count)
}

另一种风格

addCount (count) {
	return this.$store.commit({
        type: 'increment',
        count: count
    })
}

常量

在mutations中使用常量,能减少一些commit()mutations中函数名不一致的错误;

以上面的increment为例

store文件夹下的mutations-types.js

export const INCREMENT = 'increment'

Home.vue

<h2>{{ $store.state.counter }}</h2>
<button @click="add()">+</button>
import {
  INCREMENT
} from './store/mutations-types'

methods: {
  add () {
    this.$store.commit(INCREMENT)
  }
}

store文件夹下的index.js

使用 [] 获取常量中的值

import {
  INCREMENT
} from './mutations-types'

mutations: {
  [INCREMENT] (state) {
    state.counter++
  }
}

辅助函数

当然,mutations也有对应的辅助函数mapMutations() ,用法与上面的辅助函数类似;

2.4 actions

action类似于mutation;

但action提交的是mutation,而不是直接变更状态;

action可以包含任意异步操作

action中的函数的第一个参数context,是一个和store实例有相同方法和属性的context对象

dispatch() 可以返回一个Promise对象

基本使用

Home.vue

<h2>{{ $store.state.counter }}</h2>
<button @click="add()">+</button>
methods: {
  add () {
    this.$store.dispatch('increment')
  }
}

store目录下的index.js

state() {
    return {
        counter: 0
    }
},
mutations: {
    increment(state) {
        state.counter++
    }
},
action: {
    // 这里使用定时器模拟异步操作
    incrementAction(context) {
        setTimeout(() => {
            context.commit('increment')
        }, 1000)
    }
}

当你点击+按钮,会过一秒counter才加1~

dispatch()

在组件中要是想知道某一次派发有没有完成,这就需要组件对请求成功之后提示,而不是全部交给action;

这时就可以使用dispatch() 返回Promise啦;

Home.vue

<h2>{{ $store.state.counter }}</h2>
<button @click="add()">+</button>
methods: {
  add () {
    this.$store.dispatch('increment').then((res) => {
        console.log(res)
    }, (err) => {
        console.log(err)
    })
  }
}

store目录下的index.js

state() {
    return {
        counter: 0
    }
},
mutations: {
    increment(state) {
        state.counter++
    }
},
action: {
    // 这里使用定时器模拟异步操作
    incrementAction(context) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                context.commit('increment')
                resolve('提交成功!')
            }, 1000)
        }).catch((err) => {
            reject(err)
        })
    }
}

increment这个mutation提交成功之后,执行resolve(),将提交成功的结果交给组件处理~

辅助函数

同样,action也有对应的辅助函数mapAction() ,用法与上面的辅助函数类似;

2.5 module

由于使用单一状态树,应用的所有状态都会集中到store对象中;

当应用变得复杂,store对象就会变得相当臃肿

为了解决这一问题,Vuex允许将store对象分成模块(module)

每个模块有自己的state、mutation、action、getters等等;

在modules目录下的home.js

const home = {
	state() {
		return {
            counter: 0
        }
	},
    ...
}

在modules目录下的user.js

const home = {
	state() {
		return {
            counter: 1
        }
	},
    ...
}

store目录下的index.js

module: {
    home,
    user
}

Home.vue

<h2>{{ $store.state.home.counter }}</h2>
<h2>{{ $store.state.user.counter }}</h2>

最后屏幕显示0和1

命名空间

默认情况下,模块内部的 action 和 mutations 仍是注册在全局命名空间的;

这样使得多个模块能够对同一个 action 或 mutations 作出响应;

getters 同样也默认注册在全局命名空间,但是目前这并非出于功能上的目的;

如果希望使用或修改某个模块的状态时,不影响全局命名空间的其它状态

这时就需要给模块带上命名空间了,模块内新增一个namespaced: true属性;

使用带有命名空间的模块里的状态,需要用类似路径的方式;

如果需要commit提交修改,类似~;

const home = {
    namespaced: true
	state() {
		return {
            counter: 1
        }
	},
    ...
}

store目录下的index.js

module: {
    home
}

Home.vue

<h2>{{ $store.getters[home/counter] }}</h2>

最后显示1

辅助函数

同样,模块中也可以使用辅助函数;

但是注意:上面使用的辅助函数的前提是在全局命名空间中,局部命名空间使用辅助函数写法有所不同,详情看官网~;